Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions packages/das/src/webhook/github-fetcher.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import assert from "node:assert/strict";
import test from "node:test";
import type { ConfigService } from "@nestjs/config";
import type { Repository } from "typeorm";
import {
Issue,
LabelEvent,
PrFile,
PrFileContent,
PullRequest,
Repo,
Review,
} from "../entities";
import { GitHubFetcherService } from "./github-fetcher.service";

type PullFile = {
filename: string;
previous_filename?: string;
status: string;
};

type FetcherInternals = {
fetchContentBatch(
repoFullName: string,
prNumber: number,
batch: PullFile[],
owner: string,
repo: string,
token: string,
headSha: string,
baseSha: string | null,
): Promise<void>;
githubFetch(url: string, init: RequestInit): Promise<Response>;
};

const emptyRepo = <T extends object>(): Repository<T> => ({}) as Repository<T>;

void test("fetchContentBatch stores only base content for removed files", async () => {
let storedContent: Partial<PrFileContent> | null = null;
const contentRepo = {
upsert: (content: Partial<PrFileContent>): Promise<unknown> => {
storedContent = content;
return Promise.resolve();
},
} as unknown as Repository<PrFileContent>;

const service = new GitHubFetcherService(
{ getOrThrow: (): string => "123" } as unknown as ConfigService,
emptyRepo<PrFile>(),
contentRepo,
emptyRepo<PullRequest>(),
emptyRepo<Issue>(),
emptyRepo<Review>(),
emptyRepo<LabelEvent>(),
emptyRepo<Repo>(),
) as unknown as FetcherInternals;

service.githubFetch = (
_url: string,
init: RequestInit,
): Promise<Response> => {
const body = init.body;
assert.equal(typeof body, "string");
if (typeof body !== "string") {
throw new Error("Expected GraphQL request body to be a string");
}
assert.ok(body.includes('base0: object(expression: \\"BASE:src/old.ts\\"'));
assert.doesNotMatch(body, /head0:/);

return Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
data: {
repository: {
base0: {
text: "export const removed = true;\n",
byteSize: 29,
isBinary: false,
},
},
},
}),
} as Response);
};

await service.fetchContentBatch(
"owner/repo",
7,
[{ filename: "src/old.ts", status: "removed" }],
"owner",
"repo",
"token",
"HEAD",
"BASE",
);

assert.deepEqual(storedContent, {
repoFullName: "owner/repo",
prNumber: 7,
filename: "src/old.ts",
baseContent: "export const removed = true;\n",
headContent: null,
isBinary: false,
byteSize: 29,
});
});
19 changes: 12 additions & 7 deletions packages/das/src/webhook/github-fetcher.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,8 +477,11 @@ export class GitHubFetcherService implements OnModuleInit {
headSha: string,
baseSha: string | null,
): Promise<void> {
// Only fetch contents for files that have a meaningful version to fetch
const scored = files.filter((f) => f.status !== "removed");
// Only fetch contents for files that have a meaningful version to fetch.
// Removed files still need their base-side blob for scoring.
const scored = files.filter(
(f) => f.status !== "removed" || baseSha !== null,
);
if (scored.length === 0) return;

let batchSize = GRAPHQL_FILES_BATCH_SIZE;
Expand Down Expand Up @@ -537,11 +540,13 @@ export class GitHubFetcherService implements OnModuleInit {
`base${i}: object(expression: "${baseExpr}") { ... on Blob { text byteSize isBinary } }`,
);
}
// Head version (already filtered out removed files at caller)
const headExpr = this.escapeGraphql(`${headSha}:${file.filename}`);
fields.push(
`head${i}: object(expression: "${headExpr}") { ... on Blob { text byteSize isBinary } }`,
);
// Head version: skip for removed files because the path no longer exists.
if (file.status !== "removed") {
const headExpr = this.escapeGraphql(`${headSha}:${file.filename}`);
fields.push(
`head${i}: object(expression: "${headExpr}") { ... on Blob { text byteSize isBinary } }`,
);
}
}

const query = `
Expand Down