mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 10:21:08 -04:00
Nests released from drawing sets need a state before Quote: created but not yet nested or checked. Draft is appended to NestStatus (value 3) because the server still accepts integer statuses from existing callers, so existing numbers must not shift; NestStatuses.WorkflowOrder puts it first wherever the operator picks a status. Older desktops read Draft as Quote (the documented unknown-status fallback), and an older server refuses "draft", so the server must be upgraded before desktops that can set it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
209 lines
6.7 KiB
C#
209 lines
6.7 KiB
C#
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using OpenNest.Geometry;
|
|
using OpenNest.IO;
|
|
|
|
namespace OpenNest.Tests.IO;
|
|
|
|
public class NestStatusSerializationTests
|
|
{
|
|
[Fact]
|
|
public void RoundTrip_StatusAndMadeBy_Preserved()
|
|
{
|
|
var nest = CreateMinimalNest();
|
|
nest.Status = NestStatus.ToBeCut;
|
|
nest.MadeBy = "AJ";
|
|
|
|
var loaded = RoundTrip(nest);
|
|
|
|
Assert.Equal(NestStatus.ToBeCut, loaded.Status);
|
|
Assert.Equal("AJ", loaded.MadeBy);
|
|
}
|
|
|
|
[Fact]
|
|
public void RoundTrip_DefaultStatus_IsQuote()
|
|
{
|
|
var loaded = RoundTrip(CreateMinimalNest());
|
|
|
|
Assert.Equal(NestStatus.Quote, loaded.Status);
|
|
Assert.Equal("", loaded.MadeBy);
|
|
}
|
|
|
|
[Fact]
|
|
public void RoundTrip_HasBeenCut_Preserved()
|
|
{
|
|
var nest = CreateMinimalNest();
|
|
nest.Status = NestStatus.HasBeenCut;
|
|
|
|
var loaded = RoundTrip(nest);
|
|
|
|
Assert.Equal(NestStatus.HasBeenCut, loaded.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public void RoundTrip_Draft_PreservedAsName()
|
|
{
|
|
var nest = CreateMinimalNest();
|
|
nest.Status = NestStatus.Draft;
|
|
|
|
using var ms = new MemoryStream();
|
|
new NestWriter(nest).Write(ms);
|
|
using var doc = JsonDocument.Parse(ReadNestJson(ms));
|
|
ms.Position = 0;
|
|
var loaded = new NestReader(ms).Read();
|
|
|
|
Assert.Equal("Draft", doc.RootElement.GetProperty("status").GetString());
|
|
Assert.Equal(NestStatus.Draft, loaded.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public void WorkflowOrder_StartsWithDraftAndListsEveryStatusOnce()
|
|
{
|
|
Assert.Equal(NestStatus.Draft, NestStatuses.WorkflowOrder[0]);
|
|
Assert.Equal(
|
|
Enum.GetValues<NestStatus>().OrderBy(s => s),
|
|
NestStatuses.WorkflowOrder.OrderBy(s => s));
|
|
}
|
|
|
|
[Fact]
|
|
public void ExistingStatusNumbers_AreUnchanged()
|
|
{
|
|
// The nest server still accepts integer statuses from existing callers.
|
|
Assert.Equal(0, (int)NestStatus.Quote);
|
|
Assert.Equal(1, (int)NestStatus.ToBeCut);
|
|
Assert.Equal(2, (int)NestStatus.HasBeenCut);
|
|
Assert.Equal(3, (int)NestStatus.Draft);
|
|
}
|
|
|
|
[Fact]
|
|
public void Status_SerializedAsEnumString()
|
|
{
|
|
var nest = CreateMinimalNest();
|
|
nest.Status = NestStatus.ToBeCut;
|
|
nest.MadeBy = "Rex";
|
|
|
|
using var ms = new MemoryStream();
|
|
new NestWriter(nest).Write(ms);
|
|
var dtoJson = ReadNestJson(ms);
|
|
using var doc = JsonDocument.Parse(dtoJson);
|
|
|
|
// Enum strings follow the existing units convention (PascalCase).
|
|
Assert.Equal("ToBeCut", doc.RootElement.GetProperty("status").GetString());
|
|
Assert.Equal("Rex", doc.RootElement.GetProperty("madeBy").GetString());
|
|
}
|
|
|
|
[Fact]
|
|
public void LegacyFile_WithoutStatusFields_LoadsDefaults()
|
|
{
|
|
var nest = CreateMinimalNest();
|
|
using var ms = new MemoryStream();
|
|
new NestWriter(nest).Write(ms);
|
|
|
|
// Rebuild the archive with the additive fields stripped from nest.json.
|
|
var rebuilt = StripFields(ms, "status", "madeBy");
|
|
|
|
var loaded = new NestReader(rebuilt).Read();
|
|
|
|
Assert.Equal(NestStatus.Quote, loaded.Status);
|
|
Assert.Equal("", loaded.MadeBy);
|
|
}
|
|
|
|
[Fact]
|
|
public void File_WithUnknownStatusValue_LoadsQuote()
|
|
{
|
|
var nest = CreateMinimalNest();
|
|
using var ms = new MemoryStream();
|
|
new NestWriter(nest).Write(ms);
|
|
var rebuilt = ReplaceField(ms, "status", "Banana");
|
|
|
|
var loaded = new NestReader(rebuilt).Read();
|
|
|
|
Assert.Equal(NestStatus.Quote, loaded.Status);
|
|
}
|
|
|
|
private static Nest CreateMinimalNest()
|
|
{
|
|
var program = new OpenNest.CNC.Program();
|
|
program.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(10, 0)));
|
|
var drawing = new Drawing("Part", program);
|
|
var nest = new Nest { Name = "Test" };
|
|
nest.Drawings.Add(drawing);
|
|
var plate = new Plate(new Size(100, 100));
|
|
plate.Parts.Add(new Part(drawing, new Vector(0, 0)));
|
|
nest.Plates.Add(plate);
|
|
return nest;
|
|
}
|
|
|
|
private static Nest RoundTrip(Nest nest)
|
|
{
|
|
var ms = new MemoryStream();
|
|
new NestWriter(nest).Write(ms);
|
|
ms.Position = 0;
|
|
return new NestReader(ms).Read();
|
|
}
|
|
|
|
private static string ReadNestJson(MemoryStream archive)
|
|
{
|
|
archive.Position = 0;
|
|
using var zip = new ZipArchive(archive, ZipArchiveMode.Read, leaveOpen: true);
|
|
using var reader = new StreamReader(
|
|
zip.GetEntry("nest.json").Open(), Encoding.UTF8);
|
|
return reader.ReadToEnd();
|
|
}
|
|
|
|
private static MemoryStream RewriteNestJson(MemoryStream archive, string json)
|
|
{
|
|
var output = new MemoryStream();
|
|
archive.Position = 0;
|
|
using (var source = new ZipArchive(archive, ZipArchiveMode.Read, leaveOpen: true))
|
|
using (var target = new ZipArchive(output, ZipArchiveMode.Create, leaveOpen: true))
|
|
{
|
|
foreach (var entry in source.Entries)
|
|
{
|
|
var newEntry = target.CreateEntry(entry.FullName);
|
|
using var sourceStream = entry.Open();
|
|
using var targetStream = newEntry.Open();
|
|
if (entry.FullName == "nest.json")
|
|
{
|
|
using var writer = new StreamWriter(targetStream, Encoding.UTF8);
|
|
writer.Write(json);
|
|
}
|
|
else
|
|
{
|
|
sourceStream.CopyTo(targetStream);
|
|
}
|
|
}
|
|
}
|
|
output.Position = 0;
|
|
return output;
|
|
}
|
|
|
|
private static MemoryStream StripFields(MemoryStream archive, params string[] fields)
|
|
{
|
|
using var doc = JsonDocument.Parse(ReadNestJson(archive));
|
|
var options = new JsonSerializerOptions
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
};
|
|
var root = doc.RootElement.Deserialize<Dictionary<string, JsonElement>>(options);
|
|
foreach (var field in fields)
|
|
root.Remove(field);
|
|
return RewriteNestJson(archive, JsonSerializer.Serialize(root, options));
|
|
}
|
|
|
|
private static MemoryStream ReplaceField(MemoryStream archive, string field, string value)
|
|
{
|
|
using var doc = JsonDocument.Parse(ReadNestJson(archive));
|
|
var options = new JsonSerializerOptions
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
};
|
|
var root = doc.RootElement.Deserialize<Dictionary<string, JsonElement>>(options);
|
|
root[field] = JsonSerializer.Deserialize<JsonElement>($"\"{value}\"");
|
|
return RewriteNestJson(archive, JsonSerializer.Serialize(root, options));
|
|
}
|
|
}
|