| 1 | import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; |
| 2 | import { assert, it, vi } from "@effect/vitest"; |
| 3 | import { |
| 4 | CheckpointScopeId, |
| 5 | NodeId, |
| 6 | ProviderThreadId, |
| 7 | RunId, |
| 8 | ThreadId, |
| 9 | type OrchestrationV2CheckpointScope, |
| 10 | VcsProcessTimeoutError, |
| 11 | } from "@t3tools/contracts"; |
| 12 | import * as DateTime from "effect/DateTime"; |
| 13 | import * as Effect from "effect/Effect"; |
| 14 | import * as Exit from "effect/Exit"; |
| 15 | import * as Layer from "effect/Layer"; |
| 16 | |
| 17 | import * as CheckpointStore from "../checkpointing/CheckpointStore.ts"; |
| 18 | import * as CheckpointService from "./CheckpointService.ts"; |
| 19 | import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; |
| 20 | |
| 21 | it.effect.each([false, true, "interrupt"] as const)( |
| 22 | "materializes baseline, lookup fails=%s", |
| 23 | (lookupFails) => { |
| 24 | const scope: OrchestrationV2CheckpointScope = { |
| 25 | id: CheckpointScopeId.make("checkpoint-scope:materialize-baseline"), |
| 26 | threadId: ThreadId.make("thread:materialize-baseline"), |
| 27 | runId: RunId.make("run:materialize-baseline:3"), |
| 28 | nodeId: NodeId.make("node:materialize-baseline:3"), |
| 29 | parentScopeId: null, |
| 30 | providerThreadId: ProviderThreadId.make("provider-thread:materialize-baseline"), |
| 31 | kind: "root_run", |
| 32 | ordinalWithinParent: 0, |
| 33 | advancesAppRunCount: true, |
| 34 | cwd: "/repo", |
| 35 | createdAt: DateTime.makeUnsafe("2026-07-28T00:00:00.000Z"), |
| 36 | }; |
| 37 | const hasCheckpointRef = vi.fn((_input: CheckpointStore.RestoreCheckpointInput) => |
| 38 | lookupFails === "interrupt" |
| 39 | ? Effect.interrupt |
| 40 | : lookupFails |
| 41 | ? Effect.fail( |
| 42 | new VcsProcessTimeoutError({ |
| 43 | operation: "test.ref", |
| 44 | command: "git", |
| 45 | cwd: "/repo", |
| 46 | timeoutMs: 30000, |
| 47 | }), |
| 48 | ) |
| 49 | : Effect.succeed(true), |
| 50 | ); |
| 51 | const layerTest = CheckpointService.layer.pipe( |
| 52 | Layer.provide( |
| 53 | Layer.mergeAll( |
| 54 | IdAllocator.layer, |
| 55 | Layer.mock(CheckpointStore.CheckpointStore)({ |
| 56 | isGitRepository: () => Effect.succeed(true), |
| 57 | hasCheckpointRef, |
| 58 | captureCheckpoint: () => Effect.void, |
| 59 | }), |
| 60 | ), |
| 61 | ), |
| 62 | Layer.provideMerge(NodeCrypto.layer), |
| 63 | ); |
| 64 | |
| 65 | return Effect.gen(function* () { |
| 66 | const checkpoints = yield* CheckpointService.CheckpointServiceV2; |
| 67 | if (lookupFails === "interrupt") { |
| 68 | const exit = yield* Effect.exit( |
| 69 | checkpoints.materializeBaselineCheckpoint({ scope, ordinalWithinScope: 2 }), |
| 70 | ); |
| 71 | assert.isTrue(Exit.hasInterrupts(exit)); |
| 72 | const captureExit = yield* Effect.exit( |
| 73 | checkpoints.capture({ |
| 74 | scope, |
| 75 | ordinalWithinScope: 1, |
| 76 | runId: scope.runId!, |
| 77 | nodeId: scope.nodeId!, |
| 78 | appRunOrdinal: 1, |
| 79 | capturedAt: scope.createdAt, |
| 80 | }), |
| 81 | ); |
| 82 | assert.isTrue(Exit.hasInterrupts(captureExit)); |
| 83 | return; |
| 84 | } |
| 85 | const baseline = yield* checkpoints.materializeBaselineCheckpoint({ |
| 86 | scope, |
| 87 | ordinalWithinScope: 2, |
| 88 | }); |
| 89 | |
| 90 | assert.equal(baseline.ordinalWithinScope, 2); |
| 91 | assert.equal( |
| 92 | baseline.ref, |
| 93 | yield* CheckpointService.checkpointRefForScopeOrdinal({ |
| 94 | scopeId: scope.id, |
| 95 | ordinalWithinScope: 2, |
| 96 | }), |
| 97 | ); |
| 98 | assert.equal(baseline.status, lookupFails ? "missing" : "ready"); |
| 99 | assert.deepEqual(hasCheckpointRef.mock.calls[0]?.[0], { |
| 100 | cwd: scope.cwd, |
| 101 | checkpointRef: baseline.ref, |
| 102 | }); |
| 103 | }).pipe(Effect.provide(layerTest)); |
| 104 | }, |
| 105 | ); |