Files
OpenNest/OpenNest.IO.Tests/ClampZonePersistenceTests.cs
T
ajandClaude Opus 5.5 7ce5e73a82 refactor: share duplicated paging, HTTP and helper code
Roslyn duplicate scans found the same code copied across layers:
- Data: the three browse sessions share BrowseSession<TPage, TSort>;
  the remote clients share NestServerHttp (JSON options, If-Match,
  error text).
- WinForms: five paged lists share BrowsePager for loading text,
  navigation, sort glyphs and failure messages. The Add Drawings to
  Release, Drawing Library and set browser dialogs now dispose their
  browse sessions.
- Server: NestQueryRequest reuses DrawingLibraryRequest's helpers;
  revision history queries share count and paging binds.
- Engine: RemnantPlateNester delegates to DefaultPlateNester;
  CuttingPlanService reuses JointCuttingPlanSearch.Map.
- Core: LineArc and CleanHole lead-ins share LineArcApproach with
  unchanged arithmetic order, so output is bit-identical.
- IO: Dxf.GetGeometry overloads share one reader; .nest JSON test
  helpers move to NestArchiveJson.

Also fixes the production compiler warnings (CS8632, CS8603, CS0108)
and moves a test off an obsolete forwarding API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 08:45:36 -04:00

140 lines
4.9 KiB
C#

using System.IO.Compression;
using System.Text;
using OpenNest.Geometry;
using static OpenNest.IO.Tests.NestArchiveJson;
namespace OpenNest.IO.Tests;
/// <summary>
/// Clamp zones survive the .nest round trip; files without the "clamps" key load
/// unchanged with zero zones, and a clamp-carrying sheet is never dropped by the
/// empty-plate filter unless the nest's saved rail recreates exactly those zones
/// (see ClampRailPersistenceTests). Malformed zones are skipped rather than poisoning the file.
/// </summary>
public class ClampZonePersistenceTests
{
private static Nest NestWithClamps(int zoneCount)
{
var nest = new Nest("clamped");
var plate = new Plate(48, 96);
for (var i = 0; i < zoneCount; i++)
plate.ClampZones.Add(new ClampZone($"clamp-{i}", new Box(2.25 + 10 * i, 0, 1.5, 0.625)));
nest.Plates.Add(plate);
return nest;
}
private static Nest RoundTrip(Nest nest)
{
using var stream = new MemoryStream();
Assert.True(new NestWriter(nest).Write(stream));
stream.Position = 0;
return new NestReader(stream).Read();
}
[Fact]
public void ZonesRoundTripWithIdsAndPositions()
{
var reopened = RoundTrip(NestWithClamps(8));
var plate = Assert.Single(reopened.Plates);
Assert.Equal(8, plate.ClampZones.Count);
for (var i = 0; i < 8; i++)
{
var zone = plate.ClampZones[i];
Assert.Equal($"clamp-{i}", zone.Id);
Assert.Equal(2.25 + 10 * i, zone.Bounds.X, 9);
Assert.Equal(0, zone.Bounds.Y, 9);
Assert.Equal(1.5, zone.Bounds.Length, 9);
Assert.Equal(0.625, zone.Bounds.Width, 9);
}
}
[Fact]
public void ClampOnlyPlateSurvivesTheEmptyPlateFilter()
{
// A sheet with no parts and no cut-offs but with clamp definitions is a real
// configuration (the machine rail exists even on an empty plate) and must persist.
var reopened = RoundTrip(NestWithClamps(3));
var plate = Assert.Single(reopened.Plates);
Assert.Empty(plate.Parts);
Assert.Equal(3, plate.ClampZones.Count);
}
[Fact]
public void EmptyPlateWithoutClampsIsStillOmitted()
{
var nest = new Nest("empty");
nest.Plates.Add(new Plate(48, 96));
var reopened = RoundTrip(nest);
Assert.Empty(reopened.Plates);
}
[Fact]
public void ZonesCoexistWithPlacementsAndCutOffs()
{
var nest = new Nest("mixed");
var plate = new Plate(48, 96);
plate.ClampZones.Add(new ClampZone("clamp-0", new Box(5.25, 0, 1.5, 0.625)));
plate.CutOffs.Add(new CutOff(new Vector(40, 0), CutOffAxis.Vertical));
nest.Plates.Add(plate);
var reopened = RoundTrip(nest);
var reopenedPlate = Assert.Single(reopened.Plates);
Assert.Single(reopenedPlate.ClampZones);
Assert.Equal("clamp-0", reopenedPlate.ClampZones[0].Id);
Assert.Single(reopenedPlate.CutOffs);
}
[Fact]
public void FileWithoutClampsKeyLoadsUnchanged()
{
// Files written before this feature have no "clamps" property at all.
var withZones = new MemoryStream();
Assert.True(new NestWriter(NestWithClamps(2)).Write(withZones));
var legacy = RewriteNestJson(withZones,
json => System.Text.RegularExpressions.Regex.Replace(json, @",\s*""clamps""\s*:\s*\[[^\]]*\]", ""));
legacy.Position = 0;
var reopened = new NestReader(legacy).Read();
var plate = Assert.Single(reopened.Plates);
Assert.Empty(plate.Parts);
Assert.Empty(plate.ClampZones);
}
[Fact]
public void MalformedZonesAreSkippedWithoutThrowing()
{
// Hand-edited sizes must not poison the file: drop bad entries, keep good ones.
var withZones = new MemoryStream();
Assert.True(new NestWriter(NestWithClamps(1)).Write(withZones));
var tampered = RewriteNestJson(withZones, json => json.Replace(
"\"clamps\": [",
"\"clamps\": [{\"id\":\"bad\",\"x\":0,\"y\":0,\"length\":-5,\"width\":0.625},"
+ "{\"id\":\"\",\"x\":0,\"y\":0,\"length\":1.5,\"width\":0.625},")
);
tampered.Position = 0;
var reopened = new NestReader(tampered).Read();
var plate = Assert.Single(reopened.Plates);
var zone = Assert.Single(plate.ClampZones);
Assert.Equal("clamp-0", zone.Id);
}
[Fact]
public void WriterEmitsCamelCaseClampsArray()
{
var withZones = new MemoryStream();
Assert.True(new NestWriter(NestWithClamps(1)).Write(withZones));
var json = ReadNestJson(withZones);
Assert.Contains("\"clamps\": [", json, StringComparison.Ordinal);
Assert.Contains("\"id\": \"clamp-0\"", json, StringComparison.Ordinal);
Assert.Contains("\"length\": 1.5", json, StringComparison.Ordinal);
Assert.Contains("\"width\": 0.625", json, StringComparison.Ordinal);
}
}