zcbot/windows-node/Zcbot.WindowsNode.Tests/JobOfferPersistenceTests.cs

119 lines
4.5 KiB
C#

using System.Text.Json;
namespace Zcbot.WindowsNode.Tests;
public sealed class JobOfferPersistenceTests : IDisposable
{
private readonly string root = Path.Combine(
Path.GetTempPath(), "zcbot-job-offer-tests", Guid.NewGuid().ToString("N"));
[Fact]
public void AcceptedOfferIsDurableAndAConflictingDigestIsRejected()
{
var jobId = Guid.NewGuid();
var leaseId = Guid.NewGuid();
var digest = new string('d', 64);
var inbox = new JobInboxStore(root);
var adapters = new AdapterCatalog([new AcceptingAdapter()]);
var accepted = inbox.Accept(Offer(jobId, leaseId, digest), adapters);
Assert.True(accepted.Accepted);
var requestPath = Path.Combine(
root, jobId.ToString("D"), "request", "request.json");
var statePath = Path.Combine(root, jobId.ToString("D"), "state.json");
Assert.True(File.Exists(requestPath));
Assert.True(File.Exists(statePath));
using (var request = JsonDocument.Parse(File.ReadAllBytes(requestPath)))
{
Assert.Equal(
digest,
request.RootElement.GetProperty("request_digest").GetString());
Assert.Equal(
leaseId,
request.RootElement.GetProperty("lease_id").GetGuid());
}
using (var state = JsonDocument.Parse(File.ReadAllBytes(statePath)))
{
Assert.Equal("accepted", state.RootElement.GetProperty("stage").GetString());
}
var conflict = inbox.Accept(
Offer(jobId, Guid.NewGuid(), new string('e', 64)), adapters);
Assert.False(conflict.Accepted);
Assert.Equal("job_digest_conflict", conflict.Reason);
}
[Fact]
public void SameDigestWithANewLeaseUpdatesOnlyThePersistedOfferIdentity()
{
var jobId = Guid.NewGuid();
var firstLease = Guid.NewGuid();
var secondLease = Guid.NewGuid();
var digest = new string('f', 64);
var inbox = new JobInboxStore(root);
var adapters = new AdapterCatalog([new AcceptingAdapter()]);
Assert.True(inbox.Accept(Offer(jobId, firstLease, digest), adapters).Accepted);
var replay = inbox.Accept(Offer(jobId, secondLease, digest), adapters);
Assert.True(replay.Accepted);
using var request = JsonDocument.Parse(File.ReadAllBytes(Path.Combine(
root, jobId.ToString("D"), "request", "request.json")));
Assert.Equal(secondLease, request.RootElement.GetProperty("lease_id").GetGuid());
Assert.Equal(digest, request.RootElement.GetProperty("request_digest").GetString());
}
public void Dispose()
{
if (Directory.Exists(root))
{
Directory.Delete(root, recursive: true);
}
}
private static JsonElement Offer(Guid jobId, Guid leaseId, string digest)
{
var payload = JsonSerializer.SerializeToElement(new
{
job_id = jobId,
lease_id = leaseId,
request_digest = digest,
capability = "test.capability@v1",
request = new { inputs = Array.Empty<object>() },
workspace = (object?)null,
input_transfers = Array.Empty<object>(),
});
return payload;
}
private sealed class AcceptingAdapter : INodeAdapter
{
public string Capability => "test.capability@v1";
public string DisplayName => "Test";
public string AdapterVersion => "1.0.0";
public IReadOnlyList<string> Features => [];
public bool HasActiveJobs => false;
public string RunningDetail => "running";
public string? RuntimeId => null;
public bool SupportsLocalAcceptance => false;
public string RuntimePath => "test.exe";
public string ContractPath => "contract.json";
public string ContractSha256 => new('0', 64);
public string? WorkspaceStateFilename => null;
public IReadOnlyList<string> PreviewOutputIds => [];
public AdapterRuntimeStatus DetectRuntime() =>
new("Test", "1", AdapterVersion, "ready", "ready");
public void InvalidateRuntime() { }
public bool ValidateRequest(JsonElement request) => true;
public Task RunAsync(RecoverableJob job) => Task.CompletedTask;
public void Cancel(Guid jobId) { }
public Task<AdapterAcceptanceResult> RunLocalAcceptanceAsync(
string workRoot,
IProgress<string> progress,
CancellationToken cancellationToken) =>
throw new NotSupportedException();
}
}