Files
OpenNest/OpenNest.Tests/Data/SharedSettingsTests.cs
T
ajandClaude Opus 5.5 90e2fd2e6e feat(server): share machines, post settings, clamps and nest defaults
Schema v13 adds shared_settings and shared_setting_revisions behind
/api/settings: one revision-guarded JSON document per kind and key, with
every revision (deletions included) kept with who saved it. Recreating a
deleted document continues its revision count.

OpenNest.Data gains the client (RemoteSharedSettings), a synchronous store
that keeps the last copy read for offline use and refuses writes while the
server is unreachable (SharedSettingsStore), a machine provider over it, and
JSON parse/serialize entry points for clamp settings and nest defaults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 20:02:08 -04:00

134 lines
5.4 KiB
C#

using System.Text.Json;
using OpenNest.Data;
using OpenNest.Geometry;
using OpenNest.Posts.Cincinnati;
using OpenNest.Posts.CincinnatiCIFiber;
using OpenNest.Posts.GravographIS;
namespace OpenNest.Tests.Data;
/// <summary>
/// The documents every workstation shares through the nest server: their key and document
/// rules, and the JSON forms of clamps, nest defaults and post settings, which must read back
/// exactly what their files hold.
/// </summary>
public class SharedSettingsTests
{
[Theory]
[InlineData("machine", "D3F0B2A4-0D7B-4B8C-9A55-3C8B7B0F1E22", "d3f0b2a4-0d7b-4b8c-9a55-3c8b7b0f1e22")]
[InlineData("machine", "00000000-0000-0000-0000-000000000000", null)]
[InlineData("machine", "CL-980", null)]
[InlineData("post", "OpenNest.Posts.CincinnatiCIFiber.CIFiberPostProcessor", "OpenNest.Posts.CincinnatiCIFiber.CIFiberPostProcessor")]
[InlineData("post", "../escape", null)]
[InlineData("post", "", null)]
[InlineData("clamps", "default", "default")]
[InlineData("clamps", "other", null)]
[InlineData("nest-defaults", "default", "default")]
[InlineData("unknown", "default", null)]
public void NormalizeKey_AcceptsOnlyEachKindsKeys(string kind, string key, string? expected) =>
Assert.Equal(expected, SharedSettingRules.NormalizeKey(kind, key));
[Fact]
public void GetError_RequiresAJsonObjectAndAMatchingMachineId()
{
var machine = new MachineConfig { Name = "Synthetic laser" };
var json = SharedMachineProvider.Serialize(machine);
Assert.Null(SharedSettingRules.GetError("machine", machine.Id.ToString(), Write(json)));
Assert.NotNull(SharedSettingRules.GetError("machine", Guid.NewGuid().ToString(), Write(json)));
Assert.NotNull(SharedSettingRules.GetError("post", "Synthetic.Post", Write("[1, 2]")));
Assert.NotNull(SharedSettingRules.GetError("post", "Synthetic.Post", Write("{ not json")));
Assert.NotNull(SharedSettingRules.GetError("post", "Synthetic.Post", Write("")));
Assert.NotNull(SharedSettingRules.GetError("post", "Synthetic.Post", Write(new string(' ', SharedSettingRules.MaxJsonLength + 1))));
Assert.Null(SharedSettingRules.GetError("post", "Synthetic.Post", Write("{}")));
}
[Fact]
public void ClampSettings_ParseReadsWhatToJsonWrites()
{
var settings = new ClampSettings
{
Enabled = true,
Units = Units.Millimeters,
Width = 38,
Depth = 16,
RemovalMinutes = 0.5,
Centers = [150, 530],
FarEndCenters = [200],
};
var read = ClampSettings.Parse(settings.ToJson(), out var status);
Assert.Equal(ClampSettingsStatus.Ok, status);
Assert.Equal(settings.ToJson(), read.ToJson());
ClampSettings.Parse("{ not json", out var invalid);
Assert.Equal(ClampSettingsStatus.Invalid, invalid);
}
[Fact]
public void NestDefaults_ParseReadsWhatToJsonWrites()
{
var defaults = new NestDefaults
{
Units = Units.Millimeters,
Size = new Size(1500, 3000),
Quadrant = 3,
PartSpacing = 6,
EdgeSpacing = new Spacing(5, 6, 7, 8),
MachineId = Guid.NewGuid(),
};
var read = NestDefaults.Parse(defaults.ToJson(), Units.Inches, out var status);
Assert.Equal(NestDefaultsStatus.Ok, status);
Assert.Equal(defaults.ToJson(), read.ToJson());
Assert.Equal(Units.Inches, NestDefaults.Parse("{ not json", Units.Inches, out var invalid).Units);
Assert.Equal(NestDefaultsStatus.Invalid, invalid);
}
public static TheoryData<IJsonConfigPostProcessor, IJsonConfigPostProcessor, Action<object>> Posts => new()
{
{
new CincinnatiPostProcessor(new CincinnatiPostConfig { ConfigurationName = "Synthetic CL" }),
new CincinnatiPostProcessor(new CincinnatiPostConfig()),
config => Assert.Equal("Synthetic CL", ((CincinnatiPostConfig)config).ConfigurationName)
},
{
new CIFiberPostProcessor(new CIFiberPostConfig { ConfigurationName = "Synthetic Fiber" }),
new CIFiberPostProcessor(new CIFiberPostConfig()),
config => Assert.Equal("Synthetic Fiber", ((CIFiberPostConfig)config).ConfigurationName)
},
{
new GravographISPostProcessor(new GravographISPostConfig { Engrave = new LayerCutConfig { Depth = 0.125 } }),
new GravographISPostProcessor(new GravographISPostConfig()),
config => Assert.Equal(0.125, ((GravographISPostConfig)config).Engrave.Depth)
},
};
[Theory]
[MemberData(nameof(Posts))]
public void PostSettings_LoadConfigReadsWhatSerializeConfigWrites(
IJsonConfigPostProcessor source, IJsonConfigPostProcessor target, Action<object> check)
{
var json = source.SerializeConfig();
target.LoadConfig(json);
check(target.Config);
Assert.Equal(json, target.SerializeConfig());
}
[Theory]
[MemberData(nameof(Posts))]
public void PostSettings_UnusableJsonKeepsTheCurrentSettings(
IJsonConfigPostProcessor source, IJsonConfigPostProcessor _, Action<object> check)
{
Assert.ThrowsAny<JsonException>(() => source.LoadConfig("null"));
Assert.ThrowsAny<JsonException>(() => source.LoadConfig("{ not json"));
check(source.Config);
}
private static SharedSettingWrite Write(string json) => new() { Json = json, SavedBy = "tester" };
}