Ctrl K
ComunidadGuías…

scripts/refresh-test-fixtures.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env node
/**
 * refresh checked-in test fixtures for mcp/checkout.test.ts and
 * mcp/reviewComments.test.ts.
 *
 * those tests used to hit live GitHub on every run, which made them
 * cred-gated (GH_TOKEN or GITHUB_APP_ID + GITHUB_PRIVATE_KEY) and
 * non-deterministic. they now read from action/mcp/__fixtures__/*.json,
 * which this script regenerates on demand.
 *
 * run with creds set (locally via .env, or in a CI cron with secrets):
 *
 *   GH_TOKEN=… node action/scripts/refresh-test-fixtures.ts
 *   # or
 *   GITHUB_APP_ID=… GITHUB_PRIVATE_KEY=… node action/scripts/refresh-test-fixtures.ts
 *
 * commit the resulting fixture changes; review the diff before merging
 * (anything unexpected indicates real GitHub API drift).
 */

import { mkdirSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { Octokit } from "@octokit/rest";
import { config as loadDotenv } from "dotenv";
import {
  REVIEW_THREADS_QUERY,
  type ReviewThread,
  type ReviewThreadsQueryResponse,
} from "../mcp/reviewComments.ts";
import { acquireNewToken } from "../utils/github.ts";

const scriptDir = dirname(fileURLToPath(import.meta.url));
const repoRoot = resolve(scriptDir, "../..");
const fixturesDir = resolve(scriptDir, "../mcp/__fixtures__");

loadDotenv({ path: resolve(repoRoot, ".env") });

type DiffFixture = {
  owner: string;
  name: string;
  pullNumber: number;
  files: unknown;
};

type ReviewFixture = {
  owner: string;
  name: string;
  pullNumber: number;
  reviewId: number;
  review: { body: string | null | undefined; user: { login: string } | null | undefined };
  threads: ReviewThread[];
  prFiles: Array<{ filename: string; patch?: string | undefined }>;
};

const DIFF_TARGETS: Array<Pick<DiffFixture, "owner" | "name" | "pullNumber">> = [
  { owner: "pullfrog", name: "test-repo", pullNumber: 1 },
];

const REVIEW_TARGETS: Array<Pick<ReviewFixture, "owner" | "name" | "pullNumber" | "reviewId">> = [
  { owner: "pullfrog", name: "scratch", pullNumber: 49, reviewId: 3485940013 },
  { owner: "pullfrog", name: "scratch", pullNumber: 64, reviewId: 3531000326 },
];

async function getToken(): Promise<string> {
  if (process.env.GH_TOKEN) return process.env.GH_TOKEN;
  return await acquireNewToken();
}

async function refreshDiffFixture(
  octokit: Octokit,
  target: (typeof DIFF_TARGETS)[number]
): Promise<void> {
  const files = await octokit.paginate(octokit.rest.pulls.listFiles, {
    owner: target.owner,
    repo: target.name,
    pull_number: target.pullNumber,
    per_page: 100,
  });
  const fixture: DiffFixture = { ...target, files };
  const path = resolve(
    fixturesDir,
    `${target.owner}-${target.name}-pr-${target.pullNumber}.diff.json`
  );
  writeFileSync(path, `${JSON.stringify(fixture, null, 2)}\n`);
  console.log(`wrote ${path}`);
}

async function refreshReviewFixture(
  octokit: Octokit,
  target: (typeof REVIEW_TARGETS)[number]
): Promise<void> {
  const [review, threadsResp] = await Promise.all([
    octokit.rest.pulls.getReview({
      owner: target.owner,
      repo: target.name,
      pull_number: target.pullNumber,
      review_id: target.reviewId,
    }),
    octokit.graphql<ReviewThreadsQueryResponse>(REVIEW_THREADS_QUERY, {
      owner: target.owner,
      name: target.name,
      prNumber: target.pullNumber,
    }),
  ]);

  const allThreads = threadsResp.repository?.pullRequest?.reviewThreads?.nodes ?? [];
  const threads = allThreads.filter((thread): thread is ReviewThread => {
    if (!thread?.comments?.nodes) return false;
    return thread.comments.nodes.some((c) => c?.pullRequestReview?.databaseId === target.reviewId);
  });

  // skip listFiles entirely when there are no threads — prFiles is only
  // used for thread blocks, so an empty array short-circuits in the
  // formatter. mirrors getReviewData's runtime perf optimization and
  // keeps body-only-review fixtures small.
  const prFiles =
    threads.length > 0
      ? await octokit.paginate(octokit.rest.pulls.listFiles, {
          owner: target.owner,
          repo: target.name,
          pull_number: target.pullNumber,
          per_page: 100,
        })
      : [];

  // strip prFiles down to the fields the formatter actually reads. keeps
  // fixtures small and avoids capturing volatile fields (sha, blob_url,
  // contents_url, etc.) that would churn unrelated to formatter behavior.
  const trimmedFiles = prFiles.map((f) => ({
    filename: f.filename,
    ...(f.patch ? { patch: f.patch } : {}),
  }));

  const fixture: ReviewFixture = {
    ...target,
    review: {
      body: review.data.body,
      user: review.data.user ? { login: review.data.user.login } : null,
    },
    threads,
    prFiles: trimmedFiles,
  };
  const path = resolve(
    fixturesDir,
    `${target.owner}-${target.name}-pr-${target.pullNumber}-review-${target.reviewId}.json`
  );
  writeFileSync(path, `${JSON.stringify(fixture, null, 2)}\n`);
  console.log(`wrote ${path}`);
}

async function main(): Promise<void> {
  const token = await getToken();
  const octokit = new Octokit({ auth: token });
  mkdirSync(fixturesDir, { recursive: true });

  for (const t of DIFF_TARGETS) await refreshDiffFixture(octokit, t);
  for (const t of REVIEW_TARGETS) await refreshReviewFixture(octokit, t);
}

await main();