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 { 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>([]); 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)), ); }), ); });