import { describe, expect, it } from "vite-plus/test"; import { MessageId, CommandId, DEFAULT_PROVIDER_INTERACTION_MODE, ProjectId, ThreadId, type OrchestrationCommand, type OrchestrationReadModel, ProviderInstanceId, } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; import { listThreadsByProjectId, requireThread, requireThreadAbsent } from "./commandInvariants.ts"; const now = "2026-01-01T00:00:00.000Z"; const readModel: OrchestrationReadModel = { snapshotSequence: 2, updatedAt: now, projects: [ { id: ProjectId.make("project-a"), title: "Project A", workspaceRoot: "/tmp/project-a", defaultModelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5-codex", }, scripts: [], createdAt: now, updatedAt: now, deletedAt: null, }, { id: ProjectId.make("project-b"), title: "Project B", workspaceRoot: "/tmp/project-b", defaultModelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5-codex", }, scripts: [], createdAt: now, updatedAt: now, deletedAt: null, }, ], threads: [ { id: ThreadId.make("thread-1"), projectId: ProjectId.make("project-a"), title: "Thread A", modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5-codex", }, interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, runtimeMode: "full-access", branch: null, worktreePath: null, pullRequests: [], createdAt: now, updatedAt: now, archivedAt: null, settledOverride: null, settledAt: null, latestTurn: null, messages: [], session: null, activities: [], proposedPlans: [], checkpoints: [], deletedAt: null, }, { id: ThreadId.make("thread-2"), projectId: ProjectId.make("project-b"), title: "Thread B", modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5-codex", }, interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, runtimeMode: "full-access", branch: null, worktreePath: null, pullRequests: [], createdAt: now, updatedAt: now, archivedAt: null, settledOverride: null, settledAt: null, latestTurn: null, messages: [], session: null, activities: [], proposedPlans: [], checkpoints: [], deletedAt: null, }, ], }; const messageSendCommand: OrchestrationCommand = { type: "thread.turn.start", commandId: CommandId.make("cmd-1"), threadId: ThreadId.make("thread-1"), message: { messageId: MessageId.make("msg-1"), role: "user", text: "hello", attachments: [], }, interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, runtimeMode: "approval-required", createdAt: now, }; describe("commandInvariants", () => { it("lists threads by project", () => { expect( listThreadsByProjectId(readModel, ProjectId.make("project-b")).map((thread) => thread.id), ).toEqual([ThreadId.make("thread-2")]); }); it("requires existing thread", async () => { const thread = await Effect.runPromise( requireThread({ readModel, command: messageSendCommand, threadId: ThreadId.make("thread-1"), }), ); expect(thread.id).toBe(ThreadId.make("thread-1")); await expect( Effect.runPromise( requireThread({ readModel, command: messageSendCommand, threadId: ThreadId.make("missing"), }), ), ).rejects.toThrow("does not exist"); }); it("requires missing thread for create flows", async () => { await Effect.runPromise( requireThreadAbsent({ readModel, command: { type: "thread.create", commandId: CommandId.make("cmd-2"), threadId: ThreadId.make("thread-3"), projectId: ProjectId.make("project-a"), title: "new", modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5-codex", }, interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, runtimeMode: "full-access", branch: null, worktreePath: null, createdAt: now, }, threadId: ThreadId.make("thread-3"), }), ); await expect( Effect.runPromise( requireThreadAbsent({ readModel, command: { type: "thread.create", commandId: CommandId.make("cmd-3"), threadId: ThreadId.make("thread-1"), projectId: ProjectId.make("project-a"), title: "dup", modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5-codex", }, interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, runtimeMode: "full-access", branch: null, worktreePath: null, createdAt: now, }, threadId: ThreadId.make("thread-1"), }), ), ).rejects.toThrow("already exists"); }); it("lets a draft retry re-create a thread id after its first attempt was deleted", async () => { const threadId = ThreadId.make("thread-1"); const firstAttempt = readModel.threads.find((thread) => thread.id === threadId)!; const afterRollback: OrchestrationReadModel = { ...readModel, threads: readModel.threads.map((thread) => thread.id === threadId ? { ...thread, deletedAt: now, updatedAt: now } : thread, ), }; const retry: OrchestrationCommand = { type: "thread.create", commandId: CommandId.make("cmd-retry"), threadId, projectId: firstAttempt.projectId, title: firstAttempt.title, modelSelection: firstAttempt.modelSelection, interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, runtimeMode: "approval-required", branch: null, worktreePath: null, createdAt: now, }; await expect( Effect.runPromise( requireThreadAbsent({ readModel: afterRollback, command: retry, threadId }), ), ).resolves.toBeUndefined(); }); });