230 lines · 7.4 KB · TypeScript
RawDownload
import {  CommandId,  ProjectId,  ProviderInstanceId,  ThreadId,  type GitRunStackedActionResult,  type OrchestrationCommand,  type OrchestrationProjectShell,  type OrchestrationThreadShell,} from "@t3tools/contracts";import { describe, expect, it } from "@effect/vitest";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Ref from "effect/Ref";import * as Stream from "effect/Stream"; import { OrchestrationCommandInvariantError } from "../orchestration/Errors.ts";import {  OrchestrationEngineService,  type OrchestrationEngineShape,} from "../orchestration/Services/OrchestrationEngine.ts";import { ProjectionSnapshotQuery } from "../orchestration/Services/ProjectionSnapshotQuery.ts";import { createdPullRequestKey, linkCreatedPullRequest } from "./linkCreatedPullRequest.ts"; const PROJECT_ID = ProjectId.make("project-1");const THREAD_ID = ThreadId.make("thread-1");const commandId = Effect.succeed(CommandId.make("server:pr-created-link:test")); const project: OrchestrationProjectShell = {  id: PROJECT_ID,  title: "Project",  workspaceRoot: "/workspace/project",  defaultModelSelection: null,  scripts: [],  repositoryIdentity: {    canonicalKey: "github.acme.test/platform/api",    locator: {      source: "git-remote",      remoteName: "origin",      remoteUrl: "git@github.acme.test:Platform/API.git",    },    provider: "github",    displayName: "Platform/API",    owner: "Platform",    name: "API",  },  createdAt: "2026-08-01T00:00:00.000Z",  updatedAt: "2026-08-01T00:00:00.000Z",}; const thread: OrchestrationThreadShell = {  id: THREAD_ID,  projectId: PROJECT_ID,  title: "Thread",  modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5" },  runtimeMode: "full-access",  interactionMode: "default",  branch: null,  worktreePath: null,  pullRequests: [],  latestTurn: null,  createdAt: "2026-08-01T00:00:00.000Z",  updatedAt: "2026-08-20T00:00:00.000Z",  archivedAt: null,  settledOverride: null,  settledAt: null,  session: null,  latestUserMessageAt: "2026-08-20T00:00:00.000Z",  hasPendingApprovals: false,  hasPendingUserInput: false,  hasActionableProposedPlan: false,}; function prResult(pr: GitRunStackedActionResult["pr"]): Pick<GitRunStackedActionResult, "pr"> {  return { pr };} const makeDependencies = (  dispatch: OrchestrationEngineShape["dispatch"],  threadShell: OrchestrationThreadShell | null = thread,) =>  Layer.mergeAll(    Layer.mock(ProjectionSnapshotQuery)({      getThreadShellById: () => Effect.succeed(Option.fromNullishOr(threadShell)),      getProjectShellById: () => Effect.succeed(Option.some(project)),    }),    Layer.mock(OrchestrationEngineService)({      readEvents: () => Stream.empty,      dispatch,      streamDomainEvents: Stream.empty,      latestSequence: Effect.succeed(0),    }),  ); const recordingDispatch = Effect.fn("recordingDispatch")(function* () {  const commands = yield* Ref.make<ReadonlyArray<OrchestrationCommand>>([]);  const dispatch: OrchestrationEngineShape["dispatch"] = (command) =>    Ref.update(commands, (recorded) => [...recorded, command]).pipe(Effect.as({ sequence: 1 }));  return { commands, dispatch };}); describe("createdPullRequestKey", () => {  it("reads host and repository from the URL when it is recognisable", () => {    expect(      createdPullRequestKey(        prResult({          status: "created",          number: 12,          url: "https://github.com/Other/Fork/pull/12",        }),        project,      ),    ).toEqual({      host: "github.com",      repository: "other/fork",      number: 12,      url: "https://github.com/Other/Fork/pull/12",    });  });   it("falls back to the project's host and repository for an unreadable URL", () => {    expect(      createdPullRequestKey(        prResult({ status: "opened_existing", number: 3, url: "https://ghe.internal/x/3" }),        project,      ),    ).toEqual({      host: "github.acme.test",      repository: "platform/api",      number: 3,      url: "https://ghe.internal/x/3",    });    expect(      createdPullRequestKey(        prResult({ status: "created", number: 3, url: "https://ghe.internal/x/3" }),        undefined,      ),    ).toBeNull();  });   it("yields nothing when no pull request came out of the action", () => {    expect(      createdPullRequestKey(prResult({ status: "skipped_not_requested" }), project),    ).toBeNull();    expect(      createdPullRequestKey(        prResult({ status: "created", url: "https://github.com/a/b/pull/1" }),        project,      ),    ).toBeNull();    expect(createdPullRequestKey(prResult({ status: "created", number: 1 }), project)).toBeNull();  });}); describe("linkCreatedPullRequest", () => {  it.effect("links a created pull request to the thread with source created", () =>    Effect.gen(function* () {      const { commands, dispatch } = yield* recordingDispatch();      yield* linkCreatedPullRequest({        threadId: THREAD_ID,        result: prResult({          status: "created",          number: 42,          url: "https://github.com/t3tools/t3code/pull/42",        }),        commandId,      }).pipe(Effect.provide(makeDependencies(dispatch)));       expect(yield* Ref.get(commands)).toEqual([        {          type: "thread.pull-request.link",          commandId: "server:pr-created-link:test",          threadId: THREAD_ID,          host: "github.com",          repository: "t3tools/t3code",          number: 42,          url: "https://github.com/t3tools/t3code/pull/42",          source: "created",        },      ]);    }),  );   it.effect("dispatches nothing when the action produced no pull request", () =>    Effect.gen(function* () {      const { commands, dispatch } = yield* recordingDispatch();      const dependencies = makeDependencies(dispatch);      yield* linkCreatedPullRequest({        threadId: THREAD_ID,        result: prResult({ status: "skipped_not_requested" }),        commandId,      }).pipe(Effect.provide(dependencies));      yield* linkCreatedPullRequest({        threadId: THREAD_ID,        result: prResult({ status: "created", url: "https://github.com/t3tools/t3code/pull/42" }),        commandId,      }).pipe(Effect.provide(dependencies));       expect(yield* Ref.get(commands)).toEqual([]);    }),  );   it.effect("swallows an already-linked rejection and other dispatch failures", () =>    Effect.gen(function* () {      const rejecting: OrchestrationEngineShape["dispatch"] = (command) =>        Effect.fail(          new OrchestrationCommandInvariantError({            commandType: command.type,            detail: "already linked",          }),        );      const result = prResult({        status: "opened_existing",        number: 7,        url: "https://github.com/t3tools/t3code/pull/7",      });      yield* linkCreatedPullRequest({ threadId: THREAD_ID, result, commandId }).pipe(        Effect.provide(makeDependencies(rejecting)),      );      yield* linkCreatedPullRequest({ threadId: THREAD_ID, result, commandId }).pipe(        Effect.provide(makeDependencies(() => Effect.die(new Error("engine down")))),      );      // A thread that vanished between the action and the link is not an error either.      yield* linkCreatedPullRequest({ threadId: THREAD_ID, result, commandId }).pipe(        Effect.provide(makeDependencies(() => Effect.die(new Error("unreachable")), null)),      );    }),  );});