54 lines
3.0 KiB
C#
54 lines
3.0 KiB
C#
using GatewayKeepalive.Core;
|
|
|
|
namespace GatewayKeepalive.Tests;
|
|
|
|
internal static class JsonStoreTests
|
|
{
|
|
public static async Task AtomicReplacementAsync()
|
|
{
|
|
using var temp = new TempDirectory();
|
|
var nested = Path.Combine(temp.DirectoryPath, "nested");
|
|
var store = new JsonStore(nested);
|
|
var first = new SwitchJournal(
|
|
Fixtures.Baseline(),
|
|
Fixtures.Baseline() with { Dns = new DnsConfiguration(false, ["192.168.6.2"], []) },
|
|
"Prepared",
|
|
new DateTimeOffset(2026, 2, 3, 4, 5, 6, TimeSpan.Zero));
|
|
const string file = "switch-journal.json";
|
|
|
|
store.Write(file, first);
|
|
var read = store.Read<SwitchJournal>(file);
|
|
Check.True(read is not null, "A written journal should be readable");
|
|
Check.Equal(first.Stage, read!.Stage, "String data should round trip");
|
|
Check.Equal(first.StartedAt, read.StartedAt, "DateTimeOffset should round trip");
|
|
Check.True(NetworkComparison.SameConfiguration(first.Before, read.Before), "Nested before snapshot should round trip");
|
|
Check.True(NetworkComparison.SameConfiguration(first.Intended, read.Intended), "Nested intended snapshot should round trip");
|
|
Check.Equal(0, Directory.GetFiles(nested, "*.tmp").Length, "Successful write must clean its temporary file");
|
|
|
|
var second = first with { Stage = "Verified" };
|
|
store.Write(file, second);
|
|
Check.Equal("Verified", store.Read<SwitchJournal>(file)?.Stage, "The target should contain the replacement");
|
|
Check.Equal("Prepared", store.Read<SwitchJournal>(file + ".bak")?.Stage, "The previous complete file should be retained as a backup");
|
|
Check.Equal(0, Directory.GetFiles(nested, "*.tmp").Length, "Replacement must clean its temporary file");
|
|
|
|
var beforeFailure = File.ReadAllText(Path.Combine(nested, file));
|
|
var backupBeforeFailure = File.ReadAllText(Path.Combine(nested, file + ".bak"));
|
|
var failure = await Check.ThrowsAnyAsync(
|
|
() => Task.Run(() => store.Write(file, new UnserializablePayload())),
|
|
"A serialization failure should be observable");
|
|
Check.Contains("intentional serialization fault", failure.ToString(), "The expected serialization fault should surface");
|
|
Check.Equal(beforeFailure, File.ReadAllText(Path.Combine(nested, file)), "A failed write must leave the target byte-for-byte intact");
|
|
Check.Equal(backupBeforeFailure, File.ReadAllText(Path.Combine(nested, file + ".bak")), "A failed write must leave the backup intact");
|
|
Check.Equal(0, Directory.GetFiles(nested, "*.tmp").Length, "A failed write must delete its temporary file");
|
|
|
|
var empty = Path.Combine(nested, "empty.json");
|
|
File.WriteAllText(empty, string.Empty);
|
|
Check.Throws<System.Text.Json.JsonException>(() => store.Read<SwitchJournal>("empty.json"), "Malformed JSON must be rejected");
|
|
}
|
|
|
|
private sealed class UnserializablePayload
|
|
{
|
|
public string Value => throw new InvalidOperationException("intentional serialization fault");
|
|
}
|
|
}
|