summaryrefslogtreecommitdiff
path: root/tests/SomhairlesDream.Server.Tests/CoordinatorTests.fs
blob: e9689bbd4cdfdbac66feecece86b90e5b06b4419 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
module SomhairlesDream.Server.Tests.CoordinatorTests

open System
open System.IO
open System.Threading
open Xunit
open SomhairlesDream.Modeling
open SomhairlesDream.Server
open SomhairlesDream.Shared

let private ids : RunIds =
    { ProjectId = "heritage-001"
      RunId = "run-20260920-001"
      BaseId = "heritage"
      TargetId = "heritage-001" }

let private withTempDirectory action =
    let path = Path.Combine(Path.GetTempPath(), "somhairles-dream-server-" + Guid.NewGuid().ToString("N"))
    Directory.CreateDirectory(path) |> ignore

    try
        action path
    finally
        if Directory.Exists(path) then
            Directory.Delete(path, true)

type private TestBridge() =
    interface IArtifactBridge with
        member _.Export(request, _) =
            File.WriteAllBytes(request.GlbPath, [| 0x67uy; 0x6Cuy; 0x54uy; byte request.Step.Index |])
            Directory.CreateDirectory(Path.GetDirectoryName(request.LogPath)) |> ignore
            File.WriteAllText(request.LogPath, $"bridge log for {request.Step.StepId}")

            Ok
                { BlenderVersion = "5.0.1"
                  ExportedAt = DateTimeOffset.Parse("2026-09-20T12:00:05Z")
                  ObjectIds = request.Step.ObjectIds
                  VerifiedObjectIds = request.Step.ObjectIds
                  GlbBytes = 4L
                  RenderPath = None
                  RenderedAt = None }

let private waitFor predicate =
    let deadline = DateTime.UtcNow.AddSeconds(5.)
    let mutable matched = predicate ()

    while not matched && DateTime.UtcNow < deadline do
        Thread.Sleep(10)
        matched <- predicate ()

    matched

[<Fact>]
let ``coordinator starts one shared pipeline and publishes its manifest`` () =
    withTempDirectory (fun root ->
        let clock () = DateTimeOffset.Parse("2026-09-20T12:00:00Z")
        let coordinator =
            ArtifactRunCoordinator(
                root,
                (fun () -> TestBridge() :> IArtifactBridge),
                clock,
                TimeSpan.FromSeconds 15.
            )

        match coordinator.Start(ids, false) with
        | Error message -> failwith message
        | Ok(Accepted snapshot) ->
            Assert.Equal(ids.ProjectId, snapshot.ProjectId)
            Assert.Equal(ids.RunId, snapshot.RunId)
        | Ok(Conflict _) -> failwith "expected the first start to be accepted"

        let completed =
            waitFor (fun () ->
                coordinator.TryFind(ids.ProjectId, ids.RunId)
                |> Option.exists (fun store -> store.Snapshot().Status = "complete"))

        Assert.True(completed)

        match coordinator.Manifest(ids.ProjectId, ids.RunId) with
        | Error failure -> failwith $"manifest lookup failed: {failure}"
        | Ok manifest ->
            Assert.Equal("complete", manifest.Status)
            Assert.Equal(3, manifest.Steps.Length))