zcbot/windows-node/Zcbot.WindowsNode.Tests/JobRepositoryCompatibilityT...

110 lines
4.0 KiB
C#

using System.Text.Json;
namespace Zcbot.WindowsNode.Tests;
public sealed class JobRepositoryCompatibilityTests : IDisposable
{
private readonly string root = Path.Combine(
Path.GetTempPath(), "zcbot-job-repository-tests", Guid.NewGuid().ToString("N"));
[Fact]
public void ReadsExistingRequestAndStateWithoutMigration()
{
var jobId = Guid.NewGuid();
var leaseId = Guid.NewGuid();
var directory = Path.Combine(root, jobId.ToString("D"));
Directory.CreateDirectory(Path.Combine(directory, "request"));
File.WriteAllText(
Path.Combine(directory, "request", "request.json"),
JsonSerializer.Serialize(new
{
job_id = jobId,
lease_id = leaseId,
request_digest = new string('b', 64),
capability = "origin.plot@v2",
accepted_at = "2026-08-20T01:02:03Z",
request = new { },
workspace = (object?)null,
input_transfers = Array.Empty<object>(),
}));
File.WriteAllText(
Path.Combine(directory, "state.json"),
"""{"stage":"software_running","progress":10,"detail":"running","updated_at":"2026-08-20T01:03:00Z"}""");
var monitor = new JobMonitorService(root);
var repository = new JobRepository(root);
var recovered = Assert.Single(repository.ReadRecoverableJobs());
var displayed = Assert.Single(monitor.ReadSnapshots());
Assert.Equal(jobId, recovered.JobId);
Assert.Equal(leaseId, recovered.LeaseId);
Assert.Equal("software_running", displayed.Stage);
Assert.False(recovered.UploadComplete);
}
[Fact]
public void RecoveryTransitionPreservesTheExistingStateJsonShape()
{
var job = CreateJob();
var directory = Path.Combine(root, job.JobId.ToString("D"));
Directory.CreateDirectory(directory);
File.WriteAllText(
Path.Combine(directory, "state.json"),
"""{"stage":"software_running","progress":10,"detail":"running","updated_at":"2026-08-20T01:03:00Z"}""");
var repository = new JobRepository(root);
Assert.Throws<InvalidDataException>(() => repository.WriteState(
job, "downloading_inputs", 0, "retry"));
repository.WriteState(
job,
"downloading_inputs",
0,
"retry",
JobTransitionMode.Recovery);
using var state = JsonDocument.Parse(
File.ReadAllBytes(Path.Combine(directory, "state.json")));
Assert.Equal("downloading_inputs", state.RootElement.GetProperty("stage").GetString());
Assert.Equal(0, state.RootElement.GetProperty("progress").GetInt32());
Assert.Equal("retry", state.RootElement.GetProperty("detail").GetString());
Assert.True(state.RootElement.TryGetProperty("updated_at", out _));
Assert.Equal(4, state.RootElement.EnumerateObject().Count());
}
[Fact]
public void TerminalWriteRemainsIdempotent()
{
var job = CreateJob();
var repository = new JobRepository(root);
repository.WriteTerminal(job, "failed", "FIRST", "first failure");
repository.WriteTerminal(job, "cancelled", "SECOND", "late cancellation");
using var terminal = JsonDocument.Parse(File.ReadAllBytes(Path.Combine(
root, job.JobId.ToString("D"), "terminal.json")));
Assert.Equal("failed", terminal.RootElement.GetProperty("status").GetString());
Assert.Equal(
"FIRST",
terminal.RootElement.GetProperty("error").GetProperty("code").GetString());
}
public void Dispose()
{
if (Directory.Exists(root))
{
Directory.Delete(root, recursive: true);
}
}
private static RecoverableJob CreateJob() => new(
Guid.NewGuid(),
Guid.NewGuid(),
new string('c', 64),
"origin.plot@v2",
null,
null,
null,
false,
false);
}