200 lines · 5.2 KB · TypeScript
RawDownload
import { describe, expect, it } from "vitest";import {  MessageId,  CommandId,  DEFAULT_PROVIDER_INTERACTION_MODE,  ProjectId,  ThreadId,  type OrchestrationCommand,  type OrchestrationReadModel,} from "@t3tools/contracts";import { Effect } from "effect"; import {  findThreadById,  listThreadsByProjectId,  requireNonNegativeInteger,  requireThread,  requireThreadAbsent,} from "./commandInvariants.ts"; const now = new Date().toISOString(); const readModel: OrchestrationReadModel = {  snapshotSequence: 2,  updatedAt: now,  projects: [    {      id: ProjectId.makeUnsafe("project-a"),      title: "Project A",      workspaceRoot: "/tmp/project-a",      defaultModel: "gpt-5-codex",      scripts: [],      createdAt: now,      updatedAt: now,      deletedAt: null,    },    {      id: ProjectId.makeUnsafe("project-b"),      title: "Project B",      workspaceRoot: "/tmp/project-b",      defaultModel: "gpt-5-codex",      scripts: [],      createdAt: now,      updatedAt: now,      deletedAt: null,    },  ],  threads: [    {      id: ThreadId.makeUnsafe("thread-1"),      projectId: ProjectId.makeUnsafe("project-a"),      title: "Thread A",      model: "gpt-5-codex",      interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,      runtimeMode: "full-access",      branch: null,      worktreePath: null,      createdAt: now,      updatedAt: now,      latestTurn: null,      messages: [],      session: null,      activities: [],      proposedPlans: [],      checkpoints: [],      deletedAt: null,    },    {      id: ThreadId.makeUnsafe("thread-2"),      projectId: ProjectId.makeUnsafe("project-b"),      title: "Thread B",      model: "gpt-5-codex",      interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,      runtimeMode: "full-access",      branch: null,      worktreePath: null,      createdAt: now,      updatedAt: now,      latestTurn: null,      messages: [],      session: null,      activities: [],      proposedPlans: [],      checkpoints: [],      deletedAt: null,    },  ],}; const messageSendCommand: OrchestrationCommand = {  type: "thread.turn.start",  commandId: CommandId.makeUnsafe("cmd-1"),  threadId: ThreadId.makeUnsafe("thread-1"),  message: {    messageId: MessageId.makeUnsafe("msg-1"),    role: "user",    text: "hello",    attachments: [],  },  interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,  runtimeMode: "approval-required",  createdAt: now,}; describe("commandInvariants", () => {  it("finds threads by id and project", () => {    expect(findThreadById(readModel, ThreadId.makeUnsafe("thread-1"))?.projectId).toBe("project-a");    expect(findThreadById(readModel, ThreadId.makeUnsafe("missing"))).toBeUndefined();    expect(      listThreadsByProjectId(readModel, ProjectId.makeUnsafe("project-b")).map(        (thread) => thread.id,      ),    ).toEqual([ThreadId.makeUnsafe("thread-2")]);  });   it("requires existing thread", async () => {    const thread = await Effect.runPromise(      requireThread({        readModel,        command: messageSendCommand,        threadId: ThreadId.makeUnsafe("thread-1"),      }),    );    expect(thread.id).toBe(ThreadId.makeUnsafe("thread-1"));     await expect(      Effect.runPromise(        requireThread({          readModel,          command: messageSendCommand,          threadId: ThreadId.makeUnsafe("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.makeUnsafe("cmd-2"),          threadId: ThreadId.makeUnsafe("thread-3"),          projectId: ProjectId.makeUnsafe("project-a"),          title: "new",          model: "gpt-5-codex",          interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,          runtimeMode: "full-access",          branch: null,          worktreePath: null,          createdAt: now,        },        threadId: ThreadId.makeUnsafe("thread-3"),      }),    );     await expect(      Effect.runPromise(        requireThreadAbsent({          readModel,          command: {            type: "thread.create",            commandId: CommandId.makeUnsafe("cmd-3"),            threadId: ThreadId.makeUnsafe("thread-1"),            projectId: ProjectId.makeUnsafe("project-a"),            title: "dup",            model: "gpt-5-codex",            interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,            runtimeMode: "full-access",            branch: null,            worktreePath: null,            createdAt: now,          },          threadId: ThreadId.makeUnsafe("thread-1"),        }),      ),    ).rejects.toThrow("already exists");  });   it("requires non-negative integers", async () => {    await Effect.runPromise(      requireNonNegativeInteger({        commandType: "thread.checkpoint.revert",        field: "turnCount",        value: 0,      }),    );     await expect(      Effect.runPromise(        requireNonNegativeInteger({          commandType: "thread.checkpoint.revert",          field: "turnCount",          value: -1,        }),      ),    ).rejects.toThrow("greater than or equal to 0");  });});