Skip to content
Merged
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
7 changes: 7 additions & 0 deletions src/queue/ci-resolution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,11 @@ function fetchLiveCiAggregateWithRequiredContexts(
// would keep serving a stale aggregate computed against the old advisory list.
requiredContextsKey: `${resolvedRequiredContextsKeyPart(requiredContexts)}|adv:${advisoryCheckRunsKeyPart(args.advisoryCheckRuns)}|ign:${ignoredCheckRunsKeyPart(args.ignoredCheckRuns)}`,
advisoryCheckRuns: args.advisoryCheckRuns,
// #10018: thread the ignore list into the aggregate itself, not just its cache key — it was folded
// into `requiredContextsKey` above but dropped from the arg object, so `cachedFetchLiveCiAggregate`'s
// `ignoredCheckRuns` was always undefined and a maintainer-declared ignored check never actually
// dropped out of the live CI aggregate the maintenance planner reads. Mirrors advisoryCheckRuns above.
ignoredCheckRuns: args.ignoredCheckRuns,
forceRefresh: args.forceRefresh,
requiredContextsResolved: resolved,
admissionKey: args.admissionKey,
Expand Down Expand Up @@ -282,6 +287,7 @@ export function cachedLiveCiAggregate(
token: args.token,
expectedCiContexts: args.expectedCiContexts,
advisoryCheckRuns: args.advisoryCheckRuns,
ignoredCheckRuns: args.ignoredCheckRuns, // #10018: forward the ignore list, mirroring advisoryCheckRuns
forceRefresh: false,
admissionKey: args.admissionKey,
}),
Expand Down Expand Up @@ -318,6 +324,7 @@ export function refreshLiveCiAggregate(
token: args.token,
expectedCiContexts: args.expectedCiContexts,
advisoryCheckRuns: args.advisoryCheckRuns,
ignoredCheckRuns: args.ignoredCheckRuns, // #10018: forward the ignore list, mirroring advisoryCheckRuns
forceRefresh: true,
admissionKey: args.admissionKey,
}),
Expand Down
62 changes: 62 additions & 0 deletions test/unit/ci-resolution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import {
cachedLiveCiAggregate,
cachedRequiredStatusContexts,
observeRequiredContextsLookup,
refreshLiveCiAggregate,
refreshLiveMergeState,
REQUIRED_CONTEXTS_UNRESOLVED_METRIC,
reuseOrRefreshLiveCiAggregate,
setMergeStateUnknownRetryDelayMsForTest,
} from "../../src/queue/ci-resolution";
import type { LiveGithubFacts } from "../../src/queue/processors";
Expand Down Expand Up @@ -135,6 +137,66 @@ describe("cachedLiveCiAggregate request-scoped memoization (#4498)", () => {
});
});

describe("ignoredCheckRuns is threaded into the live CI aggregate, not just its cache key (#10018)", () => {
afterEach(() => {
vi.restoreAllMocks();
});
const IGNORED = [{ name: "Contributor trust", appSlug: "example-security-app" }];
const okAggregate = {
ciState: "passed" as const,
hasPending: false,
hasVisiblePending: false,
hasMissingRequiredContext: false,
failingDetails: [],
nonRequiredFailingDetails: [],
advisoryHoldDetails: [],
ignoredCheckDetails: [],
ciCompletenessWarning: null,
};
// Distinct headSha per call so the request-scoped memo never serves one test's aggregate to another.
const base = (headSha: string) => ({
repoFullName: "owner/repo",
facts: emptyFacts(),
prNumber: 7,
headSha,
baseRef: null,
token: "tok",
expectedCiContexts: null,
advisoryCheckRuns: null,
});
// fetchLiveCiAggregatePreferGraphQl's 8th positional argument (index 7) is ignoredCheckRuns.
const eighthArg = (spy: ReturnType<typeof vi.spyOn>) => spy.mock.calls[0]?.[7];

it("cachedLiveCiAggregate forwards the ignore list as the 8th positional argument", async () => {
const env = createTestEnv();
const spy = vi.spyOn(backfillModule, "fetchLiveCiAggregatePreferGraphQl").mockResolvedValue(okAggregate);
await cachedLiveCiAggregate(env, { ...base("sha-cached"), ignoredCheckRuns: IGNORED });
expect(eighthArg(spy)).toEqual(IGNORED);
});

it("refreshLiveCiAggregate forwards the ignore list as the 8th positional argument", async () => {
const env = createTestEnv();
const spy = vi.spyOn(backfillModule, "fetchLiveCiAggregatePreferGraphQl").mockResolvedValue(okAggregate);
await refreshLiveCiAggregate(env, { ...base("sha-refresh"), ignoredCheckRuns: IGNORED });
expect(eighthArg(spy)).toEqual(IGNORED);
});

it("reuseOrRefreshLiveCiAggregate (the positional-arg entry point) forwards the ignore list", async () => {
const env = createTestEnv();
const spy = vi.spyOn(backfillModule, "fetchLiveCiAggregatePreferGraphQl").mockResolvedValue(okAggregate);
// reuseOrRefreshLiveCiAggregate is positional: ignoredCheckRuns is its 11th arg (after advisoryCheckRuns).
await reuseOrRefreshLiveCiAggregate(env, "owner/repo", emptyFacts(), 7, "abc123", null, "tok", null, undefined, null, IGNORED);
expect(eighthArg(spy)).toEqual(IGNORED);
});

it("REGRESSION #10018: an unconfigured repo (ignoredCheckRuns null) still passes null/undefined through — byte-identical", async () => {
const env = createTestEnv();
const spy = vi.spyOn(backfillModule, "fetchLiveCiAggregatePreferGraphQl").mockResolvedValue(okAggregate);
await cachedLiveCiAggregate(env, { ...base("sha-null"), ignoredCheckRuns: null });
expect(eighthArg(spy) ?? null).toBeNull(); // no ignore list configured ⇒ the arg is null/undefined
});
});

describe("cachedRequiredStatusContexts resolved flag (#8358)", () => {
afterEach(() => {
vi.restoreAllMocks();
Expand Down