namespace Zcbot.WindowsNode.Tests; public sealed class PersistenceSafetyTests : IDisposable { private readonly string root = Path.Combine( Path.GetTempPath(), "zcbot-persistence-tests", Guid.NewGuid().ToString("N")); [Fact] public void PathGuardAcceptsAChildAndRejectsTraversalOrRootedSegments() { var child = PathGuard.CombineUnderRoot(root, "jobs", "job-id", "state.json"); Assert.StartsWith(Path.GetFullPath(root), child, StringComparison.OrdinalIgnoreCase); Assert.Throws( () => PathGuard.CombineUnderRoot(root, "..", "outside.json")); Assert.Throws( () => PathGuard.CombineUnderRoot(root, Path.GetPathRoot(root)!)); } [Fact] public void AtomicFileReplacesTheTargetAndLeavesNoTemporaryFile() { var path = Path.Combine(root, "jobs", "state.json"); AtomicFile.Write(path, [1, 2, 3], overwrite: false); AtomicFile.Write(path, [4, 5], overwrite: true); Assert.Equal([4, 5], File.ReadAllBytes(path)); Assert.Empty(Directory.EnumerateFiles( Path.GetDirectoryName(path)!, "state.json.tmp-*")); } public void Dispose() { if (Directory.Exists(root)) { Directory.Delete(root, recursive: true); } } }