Skip to content
Open
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
13 changes: 10 additions & 3 deletions src/signals/contributor-open-pr-monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ export async function buildContributorOpenPrMonitor(env: Env, login: string): Pr
const pendingScenarios: ContributorOpenPrMonitor["pendingScenarios"] = [];
const packets: ContributorOpenPrNextStepPacket[] = [];

for (const [repoFullName, repoOpen] of byRepo.entries()) {
for (const repoOpen of byRepo.values()) {
// The bucket is keyed case-insensitively (see groupByRepo); use a PR's original repoFullName casing for
// the case-sensitive DB lookups below so the per-repo open-PR set stays whole and queries still resolve.
const repoFullName = repoOpen[0]!.repoFullName;
const repo = repositories.find((entry) => entry.fullName.toLowerCase() === repoFullName.toLowerCase()) ?? null;
const roleContext = buildRoleContext({
login,
Expand Down Expand Up @@ -215,9 +218,13 @@ function buildMonitorGuidance(packets: ContributorOpenPrNextStepPacket[], cleanu
function groupByRepo(pullRequests: PullRequestRecord[]): Map<string, PullRequestRecord[]> {
const map = new Map<string, PullRequestRecord[]>();
for (const pr of pullRequests) {
const bucket = map.get(pr.repoFullName) ?? [];
// Key case-insensitively (GitHub repo names are case-insensitive), matching the registered-repo and
// repo-lookup handling elsewhere in buildContributorOpenPrMonitor — otherwise case-variant repoFullName
// values for one repo split into separate groups, under-counting open PRs and missing cross-case duplicates.
const key = pr.repoFullName.toLowerCase();
const bucket = map.get(key) ?? [];
bucket.push(pr);
map.set(pr.repoFullName, bucket);
map.set(key, bucket);
}
return map;
}
Expand Down
23 changes: 23 additions & 0 deletions test/unit/contributor-open-pr-monitor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,29 @@ describe("contributor open PR monitor", () => {
expect(monitor.guidance.length).toBeGreaterThan(0);
});

it("groups case-variant repoFullName for one repo into a single open-PR set", async () => {
const env = createTestEnv();
vi.spyOn(repositories, "listRepositories").mockResolvedValue([
{ fullName: "entrius/allways-ui", owner: "entrius", name: "allways-ui", isInstalled: true, isRegistered: true, isPrivate: false },
] as Awaited<ReturnType<typeof repositories.listRepositories>>);
// The same repo arrives under two casings — these must be one group, not two.
vi.spyOn(repositories, "listContributorPullRequests").mockResolvedValue([
pr({ number: 30, repoFullName: "entrius/allways-ui" }),
pr({ number: 31, repoFullName: "Entrius/Allways-UI" }),
]);
const listPrSpy = vi.spyOn(repositories, "listPullRequests").mockResolvedValue([pr({ number: 30 }), pr({ number: 31 })]);
vi.spyOn(repositories, "listPullRequestReviews").mockResolvedValue([]);
vi.spyOn(repositories, "listCheckSummaries").mockResolvedValue([]);
vi.spyOn(repositories, "listPullRequestFiles").mockResolvedValue([]);

const monitor = await buildContributorOpenPrMonitor(env, "miner-a");
expect(monitor.openPrCount).toBe(2);
// One merged group → the case-sensitive per-repo query runs only against a real repo casing, never the
// case-variant. Before the fix the two casings split into two groups and queried both.
expect(listPrSpy).toHaveBeenCalledWith(env, "entrius/allways-ui");
expect(listPrSpy).not.toHaveBeenCalledWith(env, "Entrius/Allways-UI");
});

it("keeps public monitor output free of forbidden private language", async () => {
const env = createTestEnv();
vi.spyOn(repositories, "listRepositories").mockResolvedValue([
Expand Down
Loading