3424 lines · 109.4 KB · TypeScript
RawDownload
import { assert, it } from "@effect/vitest";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as TestClock from "effect/testing/TestClock";import type {  OrchestrationProjectShell,  ProjectId,  PullRequestReviewCapabilities,  PullRequestReviewerCapabilities,  SourceControlProviderKind,} from "@t3tools/contracts"; import * as ProjectionSnapshotQuery from "../orchestration/Services/ProjectionSnapshotQuery.ts";import * as SourceControlProviderRegistry from "../sourceControl/SourceControlProviderRegistry.ts";import * as SourceControlRateLimit from "../sourceControl/SourceControlRateLimit.ts";import {  PullRequestProviderError,  type ProviderChangeRequest,  type PullRequestProviderApi,} from "./PullRequestProvider.ts";import { PullRequestProviderRegistry, fromProviders } from "./PullRequestProviderRegistry.ts";import * as PullRequestService from "./PullRequestService.ts"; function project(input: {  readonly id: string;  readonly title: string;  readonly workspaceRoot: string;  readonly repository?: string;  readonly provider?: string;  readonly host?: string;}): OrchestrationProjectShell {  // The host defaults from the provider, so a fixture only names one when the point of the  // test is two hosts of the same kind.  const host = input.host ?? (input.provider === "gitlab" ? "gitlab.com" : "github.com");  return {    id: input.id as ProjectId,    title: input.title,    workspaceRoot: input.workspaceRoot,    ...(input.repository      ? {          repositoryIdentity: {            canonicalKey: `${host}/${input.repository}`,            locator: {              source: "git-remote" as const,              remoteName: "origin",              remoteUrl: `https://${host}/${input.repository}.git`,            },            provider: input.provider ?? "github",            displayName: input.repository,          },        }      : {}),    defaultModelSelection: null,    scripts: [],    createdAt: "2026-07-01T00:00:00Z",    updatedAt: "2026-07-01T00:00:00Z",  };} function changeRequest(number: number, updatedAt: string): ProviderChangeRequest {  return {    number,    title: `Change request ${number}`,    url: `https://host/pull/${number}`,    author: { login: "octocat", name: null, avatarUrl: null },    headBranch: `feat/${number}`,    baseBranch: "main",    state: "open",    isDraft: false,    mergeability: "mergeable",    additions: 1,    deletions: 0,    createdAt: "2026-07-01T00:00:00Z",    updatedAt,    reviewRequestLogins: [],    labels: [],  };} function unusable(provider: SourceControlProviderKind, reason: "missing-tool" | "unauthenticated") {  return new PullRequestProviderError({    provider,    operation: "getViewer",    reason,    detail: `${provider} is not usable.`,  });} const requestFailed = new PullRequestProviderError({  provider: "github",  operation: "listChangeRequests",  reason: "failed",  detail: "HTTP 404",}); /** Everything a host could offer, so a fixture only narrows what its own test is about. */const FULL_REVIEW: PullRequestReviewCapabilities = {  inlineComment: true,  reply: true,  resolve: true,  verdicts: ["comment", "approve", "request-changes"],}; const FULL_REVIEWERS: PullRequestReviewerCapabilities = { request: true, listCandidates: true }; /** A provider whose every call is supplied by the test; anything unset succeeds emptily. */function fakeProvider(  kind: SourceControlProviderKind,  overrides: Partial<PullRequestProviderApi> = {},): PullRequestProviderApi {  return {    kind,    capabilities: {      diff: true,      comment: true,      actions: ["merge", "ready", "draft", "close", "reopen"],      mergeMethods: ["merge"],      search: true,      reactions: true,      review: FULL_REVIEW,      reviewers: FULL_REVIEWERS,      edit: { changeRequest: true, comment: true },    },    getViewer: () => Effect.succeed("bilal"),    // A viewer who may do everything the host can, so a test only narrows what it is about.    getViewerPermissions: () =>      Effect.succeed({        actions: ["merge", "ready", "draft", "close", "reopen"],        comment: true,        resolve: true,        verdicts: ["comment", "approve", "request-changes"],        requestReviewers: true,      }),    listChangeRequests: () => Effect.succeed({ items: [], truncated: false, continues: true }),    getChangeRequest: () => Effect.die("unused"),    getChangeRequestActivity: () => Effect.die("unused"),    getDiff: () => Effect.die("unused"),    runAction: () => Effect.void,    updateChangeRequest: () => Effect.void,    comment: () => Effect.void,    updateComment: () => Effect.void,    submitReview: () => Effect.void,    replyToThread: () => Effect.void,    setThreadResolution: () => Effect.void,    setReaction: () => Effect.void,    listReviewerCandidates: () => Effect.succeed({ candidates: [], truncated: false }),    setReviewerRequest: () => Effect.void,    ...overrides,  };} function makeService(input: {  readonly projects: ReadonlyArray<OrchestrationProjectShell>;  readonly providers: ReadonlyArray<PullRequestProviderApi>;  readonly resolveHandle?: SourceControlProviderRegistry.SourceControlProviderRegistry["Service"]["resolveHandle"];}) {  return PullRequestService.make.pipe(    Effect.provide(      Layer.mergeAll(        Layer.succeed(PullRequestProviderRegistry, fromProviders(input.providers)),        Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({          resolveHandle:            input.resolveHandle ?? (() => Effect.die("Unexpected provider refinement")),        }),        Layer.mock(ProjectionSnapshotQuery.ProjectionSnapshotQuery)({          getShellSnapshot: () =>            Effect.succeed({              snapshotSequence: 1,              projects: input.projects,              threads: [],              updatedAt: "2026-07-01T00:00:00Z",            }),        }),        SourceControlRateLimit.layer,      ),    ),  );} it.effect("refines unknown self-hosted GitLab projects before listing merge requests", () =>  Effect.gen(function* () {    let refinementCalls = 0;    const selfHosted = project({      id: "p1",      title: "self-hosted",      workspaceRoot: "/gitlab",      repository: "group/project",      provider: "unknown",      host: "code.example.test",    });    const service = yield* makeService({      projects: [        selfHosted,        { ...selfHosted, id: "p2" as ProjectId, workspaceRoot: "/gitlab-worktree" },      ],      providers: [fakeProvider("gitlab")],      resolveHandle: ({ context }) => {        refinementCalls += 1;        assert.strictEqual(context?.remoteUrl, "https://code.example.test/group/project.git");        return Effect.succeed({          context: { ...context!, provider: { ...context!.provider, kind: "gitlab" } },          provider: undefined as never,        });      },    });     const result = yield* service.list({ state: "open" });     assert.strictEqual(refinementCalls, 1);    assert.strictEqual(result.providers[0]?.host, "code.example.test");    assert.strictEqual(result.providers[0]?.kind, "gitlab");  }),); it.effect("derives a legacy repository host after refining its provider", () =>  Effect.gen(function* () {    const current = project({      id: "p1",      title: "legacy self-hosted",      workspaceRoot: "/gitlab",      repository: "group/project",      provider: "unknown",      host: "code.example.test",    });    const identity = current.repositoryIdentity!;    // Persisted identities from before canonicalKey existed are still accepted at runtime.    const legacy = {      ...current,      repositoryIdentity: {        locator: identity.locator,        provider: identity.provider,        displayName: identity.displayName,      },    } as unknown as OrchestrationProjectShell;    const service = yield* makeService({      projects: [legacy],      providers: [fakeProvider("gitlab")],      resolveHandle: ({ context }) =>        Effect.succeed({          context: { ...context!, provider: { ...context!.provider, kind: "gitlab" } },          provider: undefined as never,        }),    });     const result = yield* service.list({ state: "open", host: "gitlab" });     assert.strictEqual(result.providers[0]?.host, "gitlab");    assert.strictEqual(result.providers[0]?.kind, "gitlab");  }),); it.effect("tries another checkout when provider refinement remains unknown", () =>  Effect.gen(function* () {    const asked: string[] = [];    const selfHosted = project({      id: "p1",      title: "self-hosted",      workspaceRoot: "/gone",      repository: "group/project",      provider: "unknown",      host: "code.example.test",    });    const service = yield* makeService({      projects: [selfHosted, { ...selfHosted, id: "p2" as ProjectId, workspaceRoot: "/healthy" }],      providers: [fakeProvider("gitlab")],      resolveHandle: ({ cwd, context }) => {        asked.push(cwd);        return cwd === "/gone"          ? Effect.succeed({ context: context!, provider: undefined as never })          : Effect.succeed({              context: { ...context!, provider: { ...context!.provider, kind: "gitlab" } },              provider: undefined as never,            });      },    });     const result = yield* service.list({ state: "open" });     assert.deepStrictEqual(asked, ["/gone", "/healthy"]);    assert.strictEqual(result.providers[0]?.kind, "gitlab");  }),); /** A row as a host that reads several repositories at once hands it over. */function batchedChangeRequest(number: number, repository: string, updatedAt: string) {  return { ...changeRequest(number, updatedAt), repository };} it.effect("reads nothing from a host with no implementation, but reports it", () =>  Effect.gen(function* () {    const listed: string[] = [];    const service = yield* makeService({      projects: [        project({ id: "p1", title: "t3code", workspaceRoot: "/a", repository: "pingdotgg/t3code" }),        project({ id: "p2", title: "notes", workspaceRoot: "/b" }),        project({          id: "p3",          title: "on gitlab",          workspaceRoot: "/c",          repository: "group/project",          provider: "gitlab",        }),      ],      providers: [        fakeProvider("github", {          listChangeRequests: (input) => {            listed.push(input.repository);            return Effect.succeed({              items: [changeRequest(1, "2026-07-02T00:00:00Z")],              truncated: false,              continues: true,            });          },        }),      ],    });     const result = yield* service.list({ state: "open" });     assert.deepStrictEqual(listed, ["pingdotgg/t3code"]);    assert.strictEqual(result.entries[0]?.provider, "github");    // The GitLab project is explained rather than quietly missing from the page.    assert.deepStrictEqual(      result.providers.map((summary) => ({        kind: summary.kind,        configured: summary.configured,        projectCount: summary.projectCount,      })),      [        { kind: "github", configured: true, projectCount: 1 },        { kind: "gitlab", configured: false, projectCount: 1 },      ],    );  }),); it.effect("asks for a whole page of a host, and for the reader's own size when given one", () =>  Effect.gen(function* () {    const limits: number[] = [];    const service = yield* makeService({      projects: [        project({ id: "p1", title: "t3code", workspaceRoot: "/a", repository: "pingdotgg/t3code" }),      ],      providers: [        fakeProvider("github", {          listChangeRequests: (input) => {            limits.push(input.limit);            return Effect.succeed({ items: [], truncated: false, continues: true });          },        }),      ],    });     yield* service.list({ state: "open" });    yield* service.list({ state: "open", limit: 10 });     // Providers probe with one row over this, so 99 asks a host for 100 — the most GitHub and    // GitLab serve in one request. 100 here would cost a second round trip for a single row.    assert.deepStrictEqual(limits, [99, 10]);  }),); it.effect("says where each repository carries on, and from nothing it has run out of", () =>  Effect.gen(function* () {    const service = yield* makeService({      projects: [        project({ id: "p1", title: "t3code", workspaceRoot: "/a", repository: "pingdotgg/t3code" }),        project({ id: "p2", title: "web", workspaceRoot: "/b", repository: "acme/web" }),      ],      providers: [        fakeProvider("github", {          listChangeRequests: ({ repository }) =>            Effect.succeed({              items: [changeRequest(1, "2026-07-02T00:00:00Z")],              truncated: repository === "pingdotgg/t3code",              continues: true,            }),        }),      ],    });     const result = yield* service.list({ state: "open" });     // The instant of the oldest row, how many rows have gone, and the row already sent at that    // instant. The repository that had nothing more is simply not in it.    assert.deepStrictEqual(result.nextCursors, {      "github.com pingdotgg/t3code": "2026-07-02T00:00:00Z|1|1",    });  }),); it.effect("offers no continuation for a host that cannot be carried on from", () =>  Effect.gen(function* () {    const service = yield* makeService({      projects: [        project({ id: "p1", title: "t3code", workspaceRoot: "/a", repository: "pingdotgg/t3code" }),      ],      providers: [        fakeProvider("github", {          listChangeRequests: () =>            Effect.succeed({              items: [changeRequest(1, "2026-07-02T00:00:00Z")],              truncated: true,              continues: false,            }),        }),      ],    });     const result = yield* service.list({ state: "open" });     // More rows exist and no cursor reaches them, which is what asking for a larger page is for.    assert.isTrue(result.truncated);    assert.deepStrictEqual(result.nextCursors, {});  }),); it.effect("uses a provider's raw cursor advance when it consumed malformed rows", () =>  Effect.gen(function* () {    const service = yield* makeService({      projects: [        project({          id: "p1",          title: "web",          workspaceRoot: "/a",          repository: "acme/web",          provider: "azure-devops",          host: "dev.azure.com",        }),      ],      providers: [        fakeProvider("azure-devops", {          listChangeRequests: () =>            Effect.succeed({              items: [changeRequest(7, "2026-07-02T00:00:00Z")],              truncated: true,              cursorAdvance: 4,              continues: true,            }),        }),      ],    });     const result = yield* service.list({ state: "open" });     // Keyed by the selector Azure is actually asked with, which is the repository's own name.    assert.deepStrictEqual(result.nextCursors, {      "dev.azure.com web": "2026-07-02T00:00:00Z|4|7",    });  }),); it.effect("reads only the repositories it was asked to carry on with", () =>  Effect.gen(function* () {    const listed: string[] = [];    const cursors: Array<unknown> = [];    const service = yield* makeService({      projects: [        project({ id: "p1", title: "t3code", workspaceRoot: "/a", repository: "pingdotgg/t3code" }),        project({ id: "p2", title: "web", workspaceRoot: "/b", repository: "acme/web" }),      ],      providers: [        fakeProvider("github", {          listChangeRequests: (input) => {            listed.push(input.repository);            cursors.push(input.cursor);            return Effect.succeed({ items: [], truncated: false, continues: true });          },        }),      ],    });     const result = yield* service.list({      state: "open",      cursors: { "github.com acme/web": "2026-07-02T00:00:00Z|99|7" },    });     // The other repository is already on the page, and reading it again is the whole cost this    // is here to avoid. The host summaries stay over the workspace, because the switcher they    // fill is about the workspace rather than about this slice.    assert.deepStrictEqual(listed, ["acme/web"]);    assert.deepStrictEqual(cursors, [{ updatedBefore: "2026-07-02T00:00:00Z", delivered: 99 }]);    assert.strictEqual(result.providers.length, 1);  }),); it.effect("keeps a row already sent at the boundary instant from arriving twice", () =>  Effect.gen(function* () {    const service = yield* makeService({      projects: [        project({ id: "p1", title: "t3code", workspaceRoot: "/a", repository: "pingdotgg/t3code" }),      ],      providers: [        fakeProvider("github", {          // The boundary instant is asked for inclusively, so the host hands back the rows          // already sent at it alongside the ones beside them — which a strictly-older read          // would have lost instead.          listChangeRequests: () =>            Effect.succeed({              items: [

Lines 1–500 of 3424.