using OpenNest.Data;
namespace OpenNest.Desktop.Tests;
///
/// Which copy of the machines, nest defaults and post settings the desktop uses: the nest
/// server's once it holds one in Database mode, otherwise this PC's; the last copy read,
/// read-only, while the server cannot be reached.
///
public sealed class SharedSettingsSourceTests : 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 SharedSettingsSourceTests() => Directory.CreateDirectory(directory);
public void Dispose()
{
try { Directory.Delete(directory, recursive: true); } catch (IOException) { }
}
private string CachePath => Path.Combine(directory, "shared.json");
private string MachinesDirectory => Path.Combine(directory, "Machines");
private string DefaultsPath => Path.Combine(directory, "defaults.json");
private MachineSettingsSource Machines(SharedSettingsSource shared) =>
new(shared, user, MachinesDirectory, DefaultsPath, Path.Combine(directory, "clamps.json"), Path.Combine(directory, "grid.json"));
private static SharedSettingsSource FileMode() =>
new(() => new NestStorageSettings { Mode = NestStorageMode.File, ServerUrl = FakeSettingsServer.Url },
_ => throw new InvalidOperationException("File mode never reaches the server."));
private MachineConfig SaveLocalMachine(string name)
{
var machine = new MachineConfig { Name = name };
new LocalJsonProvider(MachinesDirectory).SaveMachine(machine);
return machine;
}
private MachineConfig HoldServerMachine(string name)
{
var machine = new MachineConfig { Name = name };
server.Hold(SharedSettingKinds.Machine, machine.Id.ToString("D"), SharedMachineProvider.Serialize(machine));
return machine;
}
private void HoldServerDefaults(double partSpacing) =>
server.Hold(SharedSettingKinds.NestDefaults, SharedSettingKinds.DefaultKey,
new NestDefaults { PartSpacing = partSpacing }.ToJson());
[Fact]
public void FileMode_UsesThisPcsMachinesAndDefaults()
{
SaveLocalMachine("Local Laser");
new NestDefaults { PartSpacing = 2 }.Save(DefaultsPath);
var shared = FileMode();
var machines = Machines(shared);
Assert.Null(shared.Current(refresh: true));
Assert.Equal(["Local Laser"], machines.LoadMachines().Select(m => m.Name));
Assert.Equal(2, machines.LoadSavedNestDefaults(out _).PartSpacing);
Assert.Equal("", shared.MachineSource(machines.CreateMachineProvider()));
Assert.Equal(("Saved on this PC.", false), shared.NestDefaultsSource());
}
[Fact]
public void DatabaseMode_TheServersMachinesReplaceThisPcsOnceItHoldsOne()
{
SaveLocalMachine("Local Laser");
HoldServerMachine("Shared Laser");
var shared = server.Source(CachePath);
var machines = Machines(shared);
Assert.Equal(["Shared Laser"], machines.LoadMachines().Select(m => m.Name));
Assert.Equal(["Local Laser"], machines.LoadLocalMachines().Select(m => m.Name));
Assert.IsType(machines.CreateMachineProvider());
Assert.Equal("shared on the nest server", shared.MachineSource(machines.CreateMachineProvider()));
}
[Fact]
public void DatabaseMode_UsesThisPcsMachinesUntilTheServerHoldsOne()
{
SaveLocalMachine("Local Laser");
HoldServerDefaults(3);
var shared = server.Source(CachePath);
var machines = Machines(shared);
Assert.Equal(["Local Laser"], machines.LoadMachines().Select(m => m.Name));
Assert.IsType(machines.CreateMachineProvider());
Assert.Equal("this PC only: not shared on the server yet", shared.MachineSource(machines.CreateMachineProvider()));
}
[Fact]
public void DatabaseMode_TheServersNestDefaultsWinAndSavesGoBackToIt()
{
new NestDefaults { PartSpacing = 2 }.Save(DefaultsPath);
HoldServerDefaults(3);
var shared = server.Source(CachePath);
var machines = Machines(shared);
Assert.Equal(3, machines.LoadSavedNestDefaults(out _).PartSpacing);
Assert.Equal(2, machines.LoadLocalNestDefaults().PartSpacing);
Assert.Equal(("Shared with every workstation on the nest server.", false), shared.NestDefaultsSource());
machines.SaveNestDefaults(new NestDefaults { PartSpacing = 4 });
var stored = server.Records.Single(r => r.Kind == SharedSettingKinds.NestDefaults);
Assert.Equal(4, NestDefaults.Parse(stored.Json, Units.Inches, out _).PartSpacing);
Assert.Equal(2, NestDefaults.Load(DefaultsPath).PartSpacing);
}
[Fact]
public void DatabaseMode_NestDefaultsTheServerDoesNotHoldSaveOnThisPc()
{
var shared = server.Source(CachePath);
var machines = Machines(shared);
Assert.Equal(("Saved on this PC only: not shared on the server yet.", false), shared.NestDefaultsSource());
machines.SaveNestDefaults(new NestDefaults { PartSpacing = 5 });
Assert.Equal(5, NestDefaults.Load(DefaultsPath).PartSpacing);
Assert.DoesNotContain(server.Records, r => r.Kind == SharedSettingKinds.NestDefaults);
}
[Fact]
public void Offline_UsesTheLastCopyReadReadOnlyAndSaysSoOnce()
{
SaveLocalMachine("Local Laser");
HoldServerMachine("Shared Laser");
HoldServerDefaults(3);
// An earlier session read the server, leaving its copy on this PC.
Assert.NotNull(server.Source(CachePath).Current(refresh: true));
server.Online = false;
var shared = server.Source(CachePath);
var machines = Machines(shared);
Assert.Equal(["Shared Laser"], machines.LoadMachines().Select(m => m.Name));
Assert.Equal(3, machines.LoadSavedNestDefaults(out _).PartSpacing);
Assert.True(shared.IsOffline);
Assert.Equal("shared, read-only: the server cannot be reached", shared.MachineSource(machines.CreateMachineProvider()));
Assert.Equal(("Shared, read-only: the server cannot be reached.", true), shared.NestDefaultsSource());
Assert.Throws(() => machines.SaveNestDefaults(new NestDefaults { PartSpacing = 4 }));
Assert.False(File.Exists(DefaultsPath));
var warning = shared.TakeOfflineWarning();
Assert.Contains("the copy read", warning);
Assert.Contains("cannot be changed", warning);
Assert.Null(shared.TakeOfflineWarning());
}
[Fact]
public void Offline_WithNoCopyReadYet_UsesThisPcsOwn()
{
SaveLocalMachine("Local Laser");
server.Online = false;
var shared = server.Source(CachePath);
var machines = Machines(shared);
Assert.Equal(["Local Laser"], machines.LoadMachines().Select(m => m.Name));
Assert.Contains("this PC's own settings", shared.TakeOfflineWarning());
}
[Fact]
public void TheServersCopyIsReReadWhenOlderThanTwoMinutesAndOfflineRetriedOnceAMinute()
{
// The store stamps each read with the real clock; the source sees this much time since it.
var later = TimeSpan.Zero;
var shared = new SharedSettingsSource(
() => new NestStorageSettings { Mode = NestStorageMode.Database, ServerUrl = FakeSettingsServer.Url },
url => new SharedSettingsStore(new RemoteSharedSettings(new HttpClient(server, false), url), CachePath, "tester"),
() => DateTime.Now + later);
shared.Current();
later = TimeSpan.FromSeconds(110);
shared.Current();
Assert.Equal(1, server.Reads);
later = TimeSpan.FromSeconds(130);
shared.Current();
Assert.Equal(2, server.Reads);
later = TimeSpan.Zero;
shared.Current(refresh: true);
Assert.Equal(3, server.Reads);
// Offline: retried after a minute rather than two.
server.Online = false;
shared.Current(refresh: true);
server.Online = true;
later = TimeSpan.FromSeconds(50);
shared.Current();
Assert.Equal(3, server.Reads);
later = TimeSpan.FromSeconds(70);
shared.Current();
Assert.Equal(4, server.Reads);
}
[Fact]
public void PendingUploads_ListWhatTheServerLacksAndNeverReplaceWhatItHolds()
{
SaveLocalMachine("Local Laser");
new NestDefaults { PartSpacing = 2 }.Save(DefaultsPath);
var post = new JsonPost();
var shared = server.Source(CachePath);
var machines = Machines(shared);
var uploads = machines.PendingUploads(shared.Current(refresh: true), [post]);
Assert.Equal(["Machine: Local Laser", "Post settings: Json Post", "Nest defaults"], uploads.Select(u => u.Label));
Assert.Contains("• Machine: Local Laser", SharedSettingsSource.DescribeUploads(uploads));
Assert.Null(SharedSettingsSource.Upload(shared.Current(), uploads));
Assert.Equal(3, server.Records.Count);
Assert.IsType(machines.CreateMachineProvider());
Assert.Empty(machines.PendingUploads(shared.Current(refresh: true), [post]));
}
[Fact]
public void PendingUploads_SkipMachinesOnceTheServerHoldsAnyAndDefaultsThisPcHasNot()
{
SaveLocalMachine("Local Laser");
HoldServerMachine("Shared Laser");
var shared = server.Source(CachePath);
var machines = Machines(shared);
Assert.Empty(machines.PendingUploads(shared.Current(refresh: true), []));
}
[Fact]
public void PostSettings_TheServersCopyIsLoadedAndAnUnreadableOneStopsThePost()
{
var post = new JsonPost();
server.Hold(SharedSettingKinds.Post, SharedSettingsSource.PostKey(post), "{\"feed\":7}");
var shared = server.Source(CachePath);
Assert.Null(shared.LoadPostSettings(post));
Assert.Equal("{\"feed\":7}", post.Loaded);
post.Refuse = true;
Assert.Contains("could not be read, so nothing was posted", shared.LoadPostSettings(post));
}
[Fact]
public void PostSettings_SaveWhereTheyAreReadAndNotWhenUnchanged()
{
var post = new JsonPost { Json = "{\"feed\":1}" };
var shared = server.Source(CachePath);
shared.SavePostSettings(post, "{\"feed\":1}");
Assert.Equal(0, post.FileSaves);
shared.SavePostSettings(post, "{\"feed\":0}");
Assert.Equal(1, post.FileSaves);
Assert.Empty(server.Records);
server.Hold(SharedSettingKinds.Post, SharedSettingsSource.PostKey(post), "{\"feed\":0}");
shared.Current(refresh: true);
shared.SavePostSettings(post, "{\"feed\":0}");
Assert.Equal(1, post.FileSaves);
Assert.Equal("{\"feed\":1}", server.Records.Single().Json);
}
internal sealed class JsonPost : IJsonConfigPostProcessor
{
public string Json { get; set; } = "{}";
public string? Loaded { get; private set; }
public bool Refuse { get; set; }
public int FileSaves { get; private set; }
public string Name => "Json Post";
public string Author => "Tests";
public string Description => "A post sharing its settings as JSON.";
public object Config => Json;
public void Post(Nest nest, Stream outputStream) { }
public void Post(Nest nest, string outputFile) { }
public void SaveConfig() => FileSaves++;
public string SerializeConfig() => Json;
public void LoadConfig(string json)
{
if (Refuse)
throw new System.Text.Json.JsonException("Not this post's settings.");
Loaded = json;
}
}
}