1363 lines · 44.3 KB · TypeScript
RawDownload
import * as Result from "effect/Result";import { describe, expect, it } from "vite-plus/test"; import {  buildReviewSubmissionJson,  buildReviewerRequestJson,  decodeBaseComparisonJson,  decodePullRequestActivityJson,  decodePullRequestDetailJson,  decodePullRequestFilesJson,  decodePullRequestListJson,  decodePullRequestNodeIdJson,  decodePullRequestSearchJson,  decodeRepositoryAccessJson,  decodeReviewerCandidatesJson,  decodeReviewThreadCommentsJson,  decodeReviewThreadsJson,  decodeViewerPermissionsJson,  reviewThreadConversation,  REVIEW_THREADS_GRAPHQL_QUERY,} from "./gitHubPullRequestJson.ts"; function listJson(entries: ReadonlyArray<Record<string, unknown>>): string {  return JSON.stringify(    entries.map((entry) => ({      number: 1,      title: "Add the pull requests page",      url: "https://github.com/pingdotgg/t3code/pull/1",      headRefName: "feat/page",      baseRefName: "main",      createdAt: "2026-07-01T00:00:00Z",      updatedAt: "2026-07-02T00:00:00Z",      ...entry,    })),  );} function expectSuccess<A>(result: Result.Result<A, unknown>): A {  expect(Result.isSuccess(result)).toBe(true);  if (!Result.isSuccess(result)) throw new Error("expected a successful decode");  return result.success;} describe("pull request list decoding", () => {  it("treats a merge timestamp as merged even when the state still says closed", () => {    const [entry] = expectSuccess(      decodePullRequestListJson(listJson([{ state: "CLOSED", mergedAt: "2026-07-03T00:00:00Z" }])),    ).items;    expect(entry?.state).toBe("merged");  });   it("normalizes mergeability and defaults unknown values", () => {    const batch = expectSuccess(      decodePullRequestListJson(        listJson([{ mergeable: "CONFLICTING" }, { mergeable: "SOMETHING_NEW" }, {}]),      ),    );    expect(batch.items.map((entry) => entry.mergeability)).toEqual([      "conflicting",      "unknown",      "unknown",    ]);  });   it("keeps user review requests and drops team ones, which are not logins", () => {    const [entry] = expectSuccess(      decodePullRequestListJson(        listJson([{ reviewRequests: [{ login: "octocat" }, { slug: "web-platform" }] }]),      ),    ).items;    expect(entry?.reviewRequestLogins).toEqual(["octocat"]);  });   it("normalizes the review decision and reports nothing for one GitHub does not summarize", () => {    const batch = expectSuccess(      decodePullRequestListJson(        listJson([          { reviewDecision: "APPROVED" },          { reviewDecision: "CHANGES_REQUESTED" },          { reviewDecision: "REVIEW_REQUIRED" },          { reviewDecision: null },        ]),      ),    );    expect(batch.items.map((entry) => entry.reviewDecision)).toEqual([      "approved",      "changes-requested",      "review-required",      null,    ]);  });   it("rolls the head commit's checks up to the one word a row has space for", () => {    const batch = expectSuccess(      decodePullRequestListJson(        listJson([          // A failure outranks a run still going, and a completed run has to be read through its          // conclusion rather than its status.          {            statusCheckRollup: [              { name: "lint", status: "COMPLETED", conclusion: "SUCCESS" },              { name: "build", status: "IN_PROGRESS" },              { name: "test", status: "COMPLETED", conclusion: "FAILURE" },            ],          },          {            statusCheckRollup: [              { name: "lint", status: "COMPLETED", conclusion: "SUCCESS" },              { name: "build", status: "QUEUED" },            ],          },          { statusCheckRollup: [{ name: "lint", status: "COMPLETED", conclusion: "SUCCESS" }] },          // A commit status reports one `state` and no `status` at all.          { statusCheckRollup: [{ context: "ci/legacy", state: "ERROR" }] },          // Neither a pass, a failure nor a wait is no verdict rather than a green tick.          { statusCheckRollup: [{ name: "lint", status: "COMPLETED", conclusion: "SKIPPED" }] },          { statusCheckRollup: [] },          {},        ]),      ),    );    expect(batch.items.map((entry) => entry.checksState)).toEqual([      "failing",      "pending",      "passing",      "failing",      null,      null,      null,    ]);  });   it("skips malformed entries but still counts them, so paging does not stop early", () => {    const raw = `[${listJson([{}]).slice(1, -1)},{"number":"not-a-number"}]`;    const batch = expectSuccess(decodePullRequestListJson(raw));    expect(batch.items).toHaveLength(1);    expect(batch.rawCount).toBe(2);  });}); describe("pull request search decoding", () => {  function searchJson(rollupStates: ReadonlyArray<string | null>): string {    return JSON.stringify({      data: {        search: {          pageInfo: { hasNextPage: false },          nodes: rollupStates.map((state, index) => ({            number: index + 1,            title: "Add the pull requests page",            url: "https://github.com/pingdotgg/t3code/pull/1",            headRefName: "feat/page",            baseRefName: "main",            createdAt: "2026-07-01T00:00:00Z",            updatedAt: "2026-07-02T00:00:00Z",            repository: { nameWithOwner: "pingdotgg/t3code" },            commits: {              nodes: [{ commit: { statusCheckRollup: state === null ? null : { state } } }],            },          })),        },      },    });  }   it("maps the rollup enum the search answers with onto the same three words", () => {    // The search asks GitHub for the verdict rather than the checks behind it, so this path sees    // one enum where the listing sees an array.    const batch = expectSuccess(      decodePullRequestSearchJson(        searchJson(["SUCCESS", "FAILURE", "ERROR", "PENDING", "EXPECTED", null]),      ),    );    expect(batch.items.map((entry) => entry.checksState)).toEqual([      "passing",      "failing",      "failing",      "pending",      "pending",      null,    ]);  });}); describe("pull request detail decoding", () => {  const detailJson = JSON.stringify({    number: 7,    title: "Detail",    url: "https://github.com/pingdotgg/t3code/pull/7",    headRefName: "feat/detail",    baseRefName: "main",    createdAt: "2026-07-01T00:00:00Z",    updatedAt: "2026-07-05T00:00:00Z",    body: "Body",    statusCheckRollup: [      { __typename: "CheckRun", name: "build", status: "IN_PROGRESS" },      { __typename: "CheckRun", name: "test", status: "COMPLETED", conclusion: "FAILURE" },      { __typename: "StatusContext", context: "ci/legacy", state: "SUCCESS" },    ],    comments: [{ id: "c1", body: "second", createdAt: "2026-07-04T00:00:00Z" }],    reviews: [      { id: "r1", body: "first", state: "CHANGES_REQUESTED", submittedAt: "2026-07-03T00:00:00Z" },      { id: "r2", body: "   ", state: "APPROVED", submittedAt: "2026-07-06T00:00:00Z" },    ],    commits: [      {        oid: "abc1234",        messageHeadline: "Ship the timeline",        committedDate: "2026-07-05T00:00:00Z",        authors: [          { login: "octocat", name: "Octo Cat", email: "octo@example.com" },          { name: "Pair Author", email: "pair@example.com" },        ],      },    ],  });   it("maps check-run status and commit-status state onto one vocabulary", () => {    const detail = expectSuccess(decodePullRequestDetailJson(detailJson));    expect(detail.checks.map((check) => [check.name, check.status])).toEqual([      ["build", "pending"],      ["test", "failure"],      ["ci/legacy", "success"],    ]);  });   it("reads an auto-merge request as armed, its null as off and its absence as neither", () => {    const raw = JSON.parse(detailJson) as Record<string, unknown>;    const armed = (entry: Record<string, unknown>) =>      expectSuccess(decodePullRequestDetailJson(JSON.stringify({ ...raw, ...entry })))        .autoMergeEnabled;     expect(      armed({ autoMergeRequest: { enabledBy: { login: "octocat" }, mergeMethod: "SQUASH" } }),    ).toBe(true);    expect(armed({ autoMergeRequest: null })).toBe(false);    // `gh` not answering for the field at all is not GitHub saying the merge is unarmed.    expect(armed({})).toBeUndefined();  });   it("shows a re-running check once, as the run that is happening now", () => {    // What `statusCheckRollup` reports while a workflow is being re-run: the same check twice,    // the finished run and the one that replaced it, with no id to tell them apart.    const raw = JSON.parse(detailJson) as Record<string, unknown>;    const detail = expectSuccess(      decodePullRequestDetailJson(        JSON.stringify({          ...raw,          statusCheckRollup: [            {              __typename: "CheckRun",              name: "Prepare PR size config",              workflowName: "PR Size",              status: "COMPLETED",              conclusion: "SUCCESS",              startedAt: "2026-08-11T16:06:20Z",              completedAt: "2026-08-11T16:06:25Z",            },            {              __typename: "CheckRun",              name: "Prepare PR size config",              workflowName: "PR Size",              status: "IN_PROGRESS",              conclusion: "",              startedAt: "2026-08-11T17:01:04Z",              completedAt: "0001-01-01T00:00:00Z",            },          ],        }),      ),    );     expect(detail.checks.map((check) => [check.name, check.status])).toEqual([      ["Prepare PR size config", "pending"],    ]);    expect(detail.checksState).toBe("pending");  });   it("merges reviews with comments in time order and keeps a bodyless approval", () => {    const detail = expectSuccess(decodePullRequestActivityJson(detailJson));    // r2 approved without writing anything, which is still the event worth seeing.    expect(detail.comments.map((comment) => comment.id)).toEqual(["r1", "c1", "r2"]);    expect(detail.comments.at(-1)?.reviewState).toBe("APPROVED");  });   it("keeps every attributed commit author, including an unlinked signature", () => {    const detail = expectSuccess(decodePullRequestActivityJson(detailJson));    expect(detail.commits[0]?.authors).toEqual([      { login: "octocat", name: "Octo Cat", avatarUrl: null },      { login: "Pair Author", name: "Pair Author", avatarUrl: null },    ]);  });   it("drops the bodyless review GitHub opens to hold line comments", () => {    const raw = JSON.parse(detailJson) as Record<string, unknown>;    const detail = expectSuccess(      decodePullRequestActivityJson(        JSON.stringify({          ...raw,          reviews: [            // What a reviewer leaving inline comments produces: a container with a state but            // nothing to read. Its comments come from the review threads instead.            { id: "r4", body: "", state: "COMMENTED", submittedAt: "2026-07-07T00:00:00Z" },            {              id: "r5",              body: "Looks good.",              state: "COMMENTED",              submittedAt: "2026-07-08T00:00:00Z",            },          ],        }),      ),    );     expect(detail.comments.map((comment) => comment.id)).toEqual(["c1", "r5"]);  });   it.each(["APPROVED", "CHANGES_REQUESTED", "DISMISSED"])(    "keeps a bodyless %s review, which is the event itself",    (state) => {      const raw = JSON.parse(detailJson) as Record<string, unknown>;      const detail = expectSuccess(        decodePullRequestActivityJson(          JSON.stringify({            ...raw,            reviews: [{ id: "r6", body: "", state, submittedAt: "2026-07-07T00:00:00Z" }],          }),        ),      );       expect(detail.comments.map((comment) => comment.id)).toContain("r6");    },  );   it("drops a review that carries neither a body nor a state", () => {    const raw = JSON.parse(detailJson) as Record<string, unknown>;    const detail = expectSuccess(      decodePullRequestActivityJson(        JSON.stringify({          ...raw,          reviews: [{ id: "r3", body: "  ", submittedAt: "2026-07-07T00:00:00Z" }],        }),      ),    );    expect(detail.comments.map((comment) => comment.id)).toEqual(["c1"]);  });}); describe("review thread decoding", () => {  const threadsJson = (    nodes: ReadonlyArray<Record<string, unknown>>,    totalCount = nodes.length,    pageInfo: Record<string, unknown> = { hasNextPage: false, endCursor: null },  ): string =>    JSON.stringify({      data: { repository: { pullRequest: { reviewThreads: { totalCount, pageInfo, nodes } } } },    });   /** The same query carries the review roster, so it is built alongside the threads. */  const reviewJson = (input: {    readonly requested?: ReadonlyArray<unknown>;    readonly reviewed?: ReadonlyArray<unknown>;  }): string =>    JSON.stringify({      data: {        repository: {          pullRequest: {            reviewThreads: { totalCount: 0, nodes: [] },            reviewRequests: {              nodes: (input.requested ?? []).map((r) => ({ requestedReviewer: r })),            },            latestReviews: { nodes: (input.reviewed ?? []).map((a) => ({ author: a })) },          },        },      },    });   it("keeps a reviewer who has already reviewed, app or person, with their avatar", () => {    const result = expectSuccess(      decodeReviewThreadsJson(        reviewJson({          requested: [{ login: "julius", name: "Julius", avatarUrl: "https://avatars/j.png" }],          // An app that has reviewed is no longer an outstanding request, which is why asking          // only for requests reported nobody on a pull request a bot had reviewed.          reviewed: [{ login: "macroscopeapp", avatarUrl: "https://avatars/in/900172.png" }],        }),      ),    );     expect(result.reviewers).toEqual([      { login: "julius", name: "Julius", avatarUrl: "https://avatars/j.png" },      { login: "macroscopeapp", name: null, avatarUrl: "https://avatars/in/900172.png" },    ]);  });   it("carries per-commit line counts from the pull-request connection", () => {    const result = expectSuccess(      decodeReviewThreadsJson(        JSON.stringify({          data: {            repository: {              pullRequest: {                reviewThreads: { totalCount: 0, nodes: [] },                commits: {                  nodes: [                    { commit: { oid: "abc123", additions: 18, deletions: 7 } },                    { commit: { oid: "def456", additions: 3, deletions: 0 } },                  ],                },              },            },          },        }),      ),    );     expect([...result.commitStats]).toEqual([      ["abc123", { additions: 18, deletions: 7 }],      ["def456", { additions: 3, deletions: 0 }],    ]);  });   it("decodes the newest commits off the same connection, oldest to newest", () => {    const result = expectSuccess(      decodeReviewThreadsJson(        JSON.stringify({          data: {            repository: {              pullRequest: {                reviewThreads: { totalCount: 0, nodes: [] },                commits: {                  nodes: [                    {                      commit: {                        oid: "abc123",                        messageHeadline: "Ship the timeline",                        committedDate: "2026-07-05T00:00:00Z",                        additions: 18,                        deletions: 7,                        authors: { nodes: [{ name: "Julius", user: { login: "julius" } }] },                      },                    },                    {                      commit: {                        oid: "def456",                        messageHeadline: "Fix the flaky test",                        committedDate: "2026-07-06T00:00:00Z",                      },                    },                  ],                },              },            },          },        }),      ),    );     expect(result.commits).toEqual([      {        oid: "abc123",        messageHeadline: "Ship the timeline",        committedDate: "2026-07-05T00:00:00Z",        authors: [{ login: "julius", name: "Julius", avatarUrl: null }],      },      {        oid: "def456",        messageHeadline: "Fix the flaky test",        committedDate: "2026-07-06T00:00:00Z",        authors: [],      },    ]);  });   it("lists someone who was asked and then answered only once", () => {    const result = expectSuccess(      decodeReviewThreadsJson(        reviewJson({          requested: [{ login: "julius", avatarUrl: "https://avatars/j.png" }],          reviewed: [{ login: "julius", avatarUrl: "https://avatars/j.png" }],        }),      ),    );     expect(result.reviewers).toHaveLength(1);  });   it("skips a team request, which names nobody to show", () => {    const result = expectSuccess(decodeReviewThreadsJson(reviewJson({ requested: [null] })));     expect(result.reviewers).toEqual([]);  });   it("keeps the conversation when a request is from a team, which has no login", () => {    // GraphQL answers with an empty object for a union member the query has no fragment for.    // Failing on it would take the whole response down, comments included.    const result = expectSuccess(      decodeReviewThreadsJson(        reviewJson({ requested: [{}, { login: "julius", avatarUrl: "https://avatars/j.png" }] }),      ),    );

Lines 1–500 of 1363.