131 lines · 3.8 KB · TypeScript
RawDownload
import {  ApprovalRequestId,  EventId,  CommandId,  ProjectId,  ProviderInstanceId,  ThreadId,  type OrchestrationReadModel,} from "@t3tools/contracts";import * as NodeServices from "@effect/platform-node/NodeServices";import { expect, it } from "@effect/vitest";import * as Effect from "effect/Effect"; import { decideOrchestrationCommand } from "./decider.ts"; const UPDATED_AT = "2026-01-01T00:00:00.000Z"; const readModel: OrchestrationReadModel = {  snapshotSequence: 0,  projects: [],  threads: [    {      id: ThreadId.make("thread-1"),      projectId: ProjectId.make("project-1"),      title: "Manual title",      modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" },      runtimeMode: "full-access",      interactionMode: "default",      pullRequests: [],      branch: null,      worktreePath: null,      latestTurn: null,      createdAt: UPDATED_AT,      updatedAt: UPDATED_AT,      archivedAt: null,      settledOverride: null,      settledAt: null,      snoozedUntil: null,      snoozedAt: null,      deletedAt: null,      messages: [],      proposedPlans: [],      activities: [],      checkpoints: [],      session: null,    },  ],  updatedAt: UPDATED_AT,}; const requestId = ApprovalRequestId.make("question-request");const command = {  type: "thread.user-input.respond" as const,  commandId: CommandId.make("answer"),  threadId: ThreadId.make("thread-1"),  requestId,  answers: { q: "" },  createdAt: UPDATED_AT,  attachmentsByQuestionId: {    q: [      {        type: "file" as const,        id: "thread-1-00000000-0000-4000-8000-0000000000aa-txt",        name: "spec.txt",        mimeType: "text/plain",        sizeBytes: 4,      },    ],  },};const request = {  id: EventId.make("question"),  kind: "user-input.requested",  summary: "Question",  tone: "info" as const,  turnId: null,  createdAt: UPDATED_AT,  payload: {    requestId,    questions: [      { id: "q", header: "Spec", question: "Provide a spec", options: [], allowCustomAnswer: true },    ],  },};it.layer(NodeServices.layer)("question attachment answers", (it) => {  it.effect("persists the original answer with its attachment and emits a provider response", () =>    Effect.gen(function* () {      const result = yield* decideOrchestrationCommand({        readModel,        command,        userInputActivity: request,      });      const events = Array.isArray(result) ? result : [result];      expect(events.map((event) => event.type)).toEqual([        "thread.activity-appended",        "thread.user-input-response-requested",      ]);      expect(events[0]?.payload).toMatchObject({        activity: {          kind: "user-input.answer-submitted",          payload: { answers: { q: "" }, attachmentsByQuestionId: command.attachmentsByQuestionId },        },      });      expect(events[1]?.payload).toMatchObject({        answers: { q: "" },        attachmentsByQuestionId: command.attachmentsByQuestionId,      });    }),  );  it.effect("rejects attachments for a resolved or unknown request", () =>    Effect.gen(function* () {      const result = yield* decideOrchestrationCommand({ readModel, command }).pipe(Effect.result);      expect(result._tag).toBe("Failure");    }),  );  it.effect("rejects unknown question IDs and predefined-choice-only protocols", () =>    Effect.gen(function* () {      for (const question of [        { ...request.payload.questions[0], id: "other" },        { ...request.payload.questions[0], allowCustomAnswer: false },      ]) {        const result = yield* decideOrchestrationCommand({          readModel,          command,          userInputActivity: { ...request, payload: { ...request.payload, questions: [question] } },        }).pipe(Effect.result);        expect(result._tag).toBe("Failure");      }    }),  );});