119 lines
4.5 KiB
C#
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();
|
|
}
|
|
}
|