using OpenNest.CNC.CuttingStrategy;
using OpenNest.Data;
namespace OpenNest.Desktop.Tests;
///
/// The shared machine entry points: the legacy clamps moving onto a machine once, cutting
/// settings saved onto the nest's machine thickness or this PC, and the nest defaults.
///
public sealed class MachineSettingsSourceTests : IDisposable
{
private readonly string directory = Path.Combine(Path.GetTempPath(), "OpenNestDesktopTests", Guid.NewGuid().ToString("N"));
private readonly FakeSettingsServer server = new();
private readonly MemoryUserSettings user = new();
public MachineSettingsSourceTests() => Directory.CreateDirectory(directory);
public void Dispose()
{
try { Directory.Delete(directory, recursive: true); } catch (IOException) { }
}
private string MachinesDirectory => Path.Combine(directory, "Machines");
private string DefaultsPath => Path.Combine(directory, "defaults.json");
private string ClampsPath => Path.Combine(directory, "clamps.json");
private static SharedSettingsSource FileMode() =>
new(() => new NestStorageSettings(), _ => throw new InvalidOperationException("File mode never reaches the server."));
private MachineSettingsSource Machines(SharedSettingsSource? shared = null) =>
new(shared ?? FileMode(), user, MachinesDirectory, DefaultsPath, ClampsPath, Path.Combine(directory, "grid.json"));
private MachineConfig SaveLocalMachine(string name)
{
var machine = new MachineConfig { Name = name };
new LocalJsonProvider(MachinesDirectory).SaveMachine(machine);
return machine;
}
private MachineConfig Reload(MachineConfig machine) => new LocalJsonProvider(MachinesDirectory).GetMachine(machine.Id)!;
private static ClampSettings Legacy() => new()
{
Enabled = true,
Units = Units.Inches,
Width = 2,
Depth = 0.5,
RemovalMinutes = 4,
Centers = [12, 24],
};
[Fact]
public void LegacyClamps_GoOntoTheOnlyMachineOnce()
{
var only = SaveLocalMachine("Only");
Legacy().Save(ClampsPath);
var machines = Machines();
Assert.Null(machines.MigrateLegacyClamps(_ => throw new InvalidOperationException("Nothing to ask.")));
Assert.Equal([12.0, 24.0], Reload(only).Clamps!.Centers);
// Once a machine has clamps the legacy file is never read again.
File.Delete(ClampsPath);
new ClampSettings { Enabled = true, Units = Units.Inches, Width = 1, Depth = 1, Centers = [99] }.Save(ClampsPath);
Assert.Null(machines.MigrateLegacyClamps(null!));
Assert.Equal([12.0, 24.0], Reload(only).Clamps!.Centers);
}
[Fact]
public void LegacyClamps_GoOntoTheNestDefaultsMachine()
{
var a = SaveLocalMachine("A");
var b = SaveLocalMachine("B");
new NestDefaults { MachineId = b.Id }.Save(DefaultsPath);
Legacy().Save(ClampsPath);
Assert.Null(Machines().MigrateLegacyClamps(_ => throw new InvalidOperationException("Nothing to ask.")));
Assert.Null(Reload(a).Clamps);
Assert.NotNull(Reload(b).Clamps);
}
[Fact]
public void LegacyClamps_WithSeveralMachinesAndNoDefault_AskOncePerSession()
{
var a = SaveLocalMachine("A");
var b = SaveLocalMachine("B");
Legacy().Save(ClampsPath);
var machines = Machines();
var asked = new List>();
machines.MigrateLegacyClamps(choices => { asked.Add(choices.Select(m => m.Name).ToList()); return null; });
machines.MigrateLegacyClamps(choices => { asked.Add(choices.Select(m => m.Name).ToList()); return choices[1]; });
Assert.Equal([["A", "B"]], asked);
Assert.Null(Reload(a).Clamps);
Assert.Null(Reload(b).Clamps);
// The next session asks again, and the machine picked gets the clamps.
Assert.Null(Machines().MigrateLegacyClamps(choices => choices[1]));
Assert.Null(Reload(a).Clamps);
Assert.NotNull(Reload(b).Clamps);
}
[Fact]
public void LegacyClamps_ComeFromTheServersCopyInDatabaseMode()
{
var only = new MachineConfig { Name = "Shared Laser" };
server.Hold(SharedSettingKinds.Machine, only.Id.ToString("D"), SharedMachineProvider.Serialize(only));
server.Hold(SharedSettingKinds.Clamps, SharedSettingKinds.DefaultKey, Legacy().ToJson());
var machines = Machines(server.Source(Path.Combine(directory, "shared.json")));
Assert.Null(machines.MigrateLegacyClamps(null!));
var stored = server.Records.Single(r => r.Kind == SharedSettingKinds.Machine);
Assert.Equal(2, stored.Revision);
Assert.NotNull(machines.LoadMachines().Single().Clamps);
}
[Fact]
public void LegacyClamps_ThatCannotBeSavedAreReported()
{
var only = new MachineConfig { Name = "Shared Laser" };
server.Hold(SharedSettingKinds.Machine, only.Id.ToString("D"), SharedMachineProvider.Serialize(only));
Legacy().Save(ClampsPath);
var shared = server.Source(Path.Combine(directory, "shared.json"));
var machines = Machines(shared);
shared.Current(refresh: true);
server.Online = false;
var warning = machines.MigrateLegacyClamps(null!);
Assert.Contains("could not be copied to a machine", warning);
Assert.Equal(1, server.Records.Single().Revision);
}
private static MachineConfig CuttingMachine() => new()
{
Name = "Fiber 1",
Materials =
{
new MaterialConfig
{
Name = "Mild Steel",
Grade = "A36",
Thicknesses = { new ThicknessConfig { Value = 0.25, LeadIn = new LeadConfig { Type = "Line", Length = 0.1, Angle = 60 } } },
},
},
};
private static Nest NestFor(MachineConfig machine, double thickness) => new()
{
Thickness = thickness,
Material = new Material("Mild Steel", "A36", 0.2836),
Machine = new NestMachine(machine.Id, machine.Name),
};
private static CuttingParameters Edited() => new()
{
ExternalLeadIn = new LineLeadIn { Length = 0.2, ApproachAngle = 45 },
SupportTabs = true,
TabBySize = true,
TabSizeMaxWidth = 2,
TabConfig = new NormalTab { Size = 0.03 },
};
[Fact]
public void CuttingSettings_SaveOntoTheMachinesThicknessAndDropThisPcsCopyForThatJob()
{
var machine = CuttingMachine();
var provider = new LocalJsonProvider(MachinesDirectory);
provider.SaveMachine(machine);
var machines = Machines();
var nest = NestFor(machine, 0.25);
var stale = new CuttingParameters();
MachineCuttingDefaults.ApplyTo(stale, machine, nest);
machines.SaveLastCuttingParameters(stale);
var warnings = new List();
Assert.True(machines.SaveCuttingParameters(nest, Edited(), warnings.Add));
Assert.Empty(warnings);
var thickness = Reload(machine).Materials[0].Thicknesses[0];
Assert.Equal(0.2, thickness.LeadIn.Length);
Assert.True(thickness.Cutting!.TabBySize);
Assert.Equal(2, thickness.Cutting.TabSizeMaxWidth);
Assert.False(MachineCuttingDefaults.IsRecordedFor(machines.LastCuttingParameters(), machine, nest));
Assert.True(machines.HasMachineThickness(nest));
Assert.Equal("Saved for Fiber 1 ยท Mild Steel A36 0.25 in", machines.DescribeCuttingSource(nest));
// The machine's thickness is what the next plate starts from.
Assert.Equal(0.2, ((LineLeadIn)machines.DefaultCuttingParameters(nest).ExternalLeadIn).Length);
}
[Fact]
public void CuttingSettings_SaveOnThisPcWhenTheMachineHasNoSuchThickness()
{
var machine = CuttingMachine();
new LocalJsonProvider(MachinesDirectory).SaveMachine(machine);
var machines = Machines();
var nest = NestFor(machine, 0.5);
Assert.False(machines.SaveCuttingParameters(nest, Edited(), _ => Assert.Fail("No warning expected.")));
Assert.Null(Reload(machine).Materials[0].Thicknesses[0].Cutting);
Assert.True(machines.LastCuttingParameters().TabBySize);
Assert.Null(machines.LastCuttingParameters().MachineName);
Assert.Equal("This PC's settings: Fiber 1 has no Mild Steel A36 0.5 in.", machines.DescribeCuttingSource(nest));
}
[Fact]
public void CuttingSettings_SaveOnThisPcWhenTheNestHasNoMachine()
{
var machines = Machines();
var nest = new Nest { Thickness = 0.25, Material = new Material("Mild Steel", "A36", 0.2836) };
Assert.False(machines.SaveCuttingParameters(nest, Edited(), _ => Assert.Fail("No warning expected.")));
Assert.True(machines.LastCuttingParameters().TabBySize);
Assert.Equal("This PC's settings: the nest has no machine.", machines.DescribeCuttingSource(nest));
}
[Fact]
public void CuttingSettings_KeptOnThisPcForThatJobWhenTheServerIsOffline()
{
var machine = CuttingMachine();
server.Hold(SharedSettingKinds.Machine, machine.Id.ToString("D"), SharedMachineProvider.Serialize(machine));
var shared = server.Source(Path.Combine(directory, "shared.json"));
var machines = Machines(shared);
var nest = NestFor(machine, 0.25);
shared.Current(refresh: true);
server.Online = false;
var warnings = new List();
Assert.False(machines.SaveCuttingParameters(nest, Edited(), warnings.Add));
Assert.Contains("could not be saved to Fiber 1", Assert.Single(warnings));
Assert.Equal(1, server.Records.Single().Revision);
var last = machines.LastCuttingParameters();
Assert.True(MachineCuttingDefaults.IsRecordedFor(last, machine, nest));
Assert.True(last.TabBySize);
// Recorded for the job, so the machine's settings do not replace the edit here.
Assert.Equal(0.2, ((LineLeadIn)machines.DefaultCuttingParameters(nest).ExternalLeadIn).Length);
}
[Fact]
public void CuttingParametersFor_UsesThePlatesOwnOnlyWhenNoneAreSaved()
{
var machines = Machines();
var plate = new Plate { CuttingParameters = new CuttingParameters { TabBySize = true } };
var nest = new Nest();
Assert.Same(plate.CuttingParameters, machines.CuttingParametersFor(plate, nest));
machines.SaveLastCuttingParameters(new CuttingParameters());
Assert.NotSame(plate.CuttingParameters, machines.CuttingParametersFor(plate, nest));
}
[Fact]
public void NestDefaults_FallBackToTheDefaultUnitAndAnUnreadableFileWarnsOnce()
{
user.DefaultUnit = Units.Millimeters;
var machines = Machines();
Assert.Equal(Units.Millimeters, machines.LoadNestDefaults(out var none).Units);
Assert.Null(none);
File.WriteAllText(DefaultsPath, "{ not json");
machines.LoadNestDefaults(out var first);
machines.LoadNestDefaults(out var second);
Assert.Contains(DefaultsPath, first);
Assert.Null(second);
}
[Fact]
public void DefaultMachine_IsTheDefaultsMachineElseTheFirstByName()
{
var b = new MachineConfig { Name = "b" };
var a = new MachineConfig { Name = "A" };
Assert.Same(a, MachineSettingsSource.DefaultMachine(new NestDefaults(), [b, a]));
Assert.Same(b, MachineSettingsSource.DefaultMachine(new NestDefaults { MachineId = b.Id }, [b, a]));
Assert.Null(MachineSettingsSource.DefaultMachine(null!, []));
}
[Fact]
public void LocalMachines_StartWithTheBuiltInMachine()
{
var loaded = Machines().LoadMachines();
Assert.NotEmpty(loaded);
Assert.True(Directory.GetFiles(MachinesDirectory, "*.json").Length > 0);
}
[Fact]
public void UserSettingsFile_KeepsBothSettingsUnderTheWinFormsNames()
{
var path = Path.Combine(directory, "user-settings.json");
UserSettingsFile.Write(path, new Dictionary { ["DefaultUnit"] = "Millimeters", ["Other"] = "kept" });
var settings = new MachineUserSettingsFile(path);
Assert.Equal(Units.Millimeters, settings.DefaultUnit);
Assert.Equal("", settings.CuttingParametersJson);
settings.CuttingParametersJson = "{\"a\":1}";
var values = UserSettingsFile.Read(path);
Assert.Equal("{\"a\":1}", values["CuttingParametersJson"]);
Assert.Equal("kept", values["Other"]);
Assert.Equal("{\"a\":1}", settings.CuttingParametersJson);
UserSettingsFile.Write(path, new Dictionary { ["DefaultUnit"] = "Furlongs" });
Assert.Equal(Units.Inches, settings.DefaultUnit);
UserSettingsFile.Write(path, new Dictionary { ["DefaultUnit"] = "7" });
Assert.Equal(Units.Inches, settings.DefaultUnit);
}
[Fact]
public void CutOffDefaults_ComeFromTheNestsMachineThickness()
{
var machine = CuttingMachine();
machine.Materials[0].Thicknesses[0].CutOff = new CutOffConfig { PartClearance = 0.75 };
new LocalJsonProvider(MachinesDirectory).SaveMachine(machine);
var settings = new CutOffSettings { PartClearance = 0.1 };
Machines().ApplyCutOffDefaults(settings, NestFor(machine, 0.25));
Assert.Equal(0.75, settings.PartClearance);
}
}