Skip to content

Commit dd404b8

Browse files
test(miner): mirror shouldReenter under root vitest for Codecov (#8347) (#8479)
1 parent f1b5cc1 commit dd404b8

2 files changed

Lines changed: 144 additions & 1 deletion

File tree

packages/loopover-engine/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ Codecov (`codecov/patch`) only reads the root vitest suite, so modules that need
3737
have a root test under `test/unit/` (e.g. `test/unit/reviewer-consensus-calibration.test.ts` for
3838
`src/reviewer-consensus-calibration.ts`#8349, or `test/unit/signal-tracking.test.ts` for
3939
`src/calibration/signal-tracking.ts`#8343, or `test/unit/harness-submission-trigger.test.ts` for
40-
`src/miner/harness-submission-trigger.ts`#8346). The package-local `node:test` suite remains the package's
40+
`src/miner/harness-submission-trigger.ts`#8346, or `test/unit/loop-reentry-policy.test.ts` for
41+
`src/miner/loop-reentry-policy.ts`#8347). The package-local `node:test` suite remains the package's
4142
own gate; the root mirror is what makes the same scenarios gradeable by Codecov.
4243

4344
## `opportunity-ranker`
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { describe, expect, it } from "vitest";
2+
import {
3+
DEFAULT_MAX_CONSECUTIVE_DISENGAGEMENTS,
4+
DEFAULT_MAX_REENTRIES_PER_HOUR,
5+
DEFAULT_MAX_REENTRIES_PER_SESSION,
6+
shouldReenter,
7+
type LoopReentryCandidate,
8+
} from "../../packages/loopover-engine/src/index";
9+
10+
/** Mirrors packages/loopover-engine/test/loop-reentry-policy.test.ts — vitest root coverage for Codecov (#8347). */
11+
function baseCandidate(overrides: Partial<LoopReentryCandidate> = {}): LoopReentryCandidate {
12+
return {
13+
killSwitchScope: "none",
14+
repoFullName: "acme/widgets",
15+
outcome: "merged",
16+
consecutiveDisengagements: 0,
17+
reentriesThisHour: 0,
18+
reentriesThisSession: 0,
19+
...overrides,
20+
};
21+
}
22+
23+
describe("shouldReenter (vitest mirror of engine node:test suite, #8347)", () => {
24+
it("barrel: the public entrypoint re-exports the loop-reentry policy (#2338)", () => {
25+
expect(typeof shouldReenter).toBe("function");
26+
expect(typeof DEFAULT_MAX_CONSECUTIVE_DISENGAGEMENTS).toBe("number");
27+
});
28+
29+
it("a merged outcome with every counter well within limits re-enters cleanly", () => {
30+
expect(shouldReenter(baseCandidate({ outcome: "merged" }))).toEqual({ reenter: true, reasons: [] });
31+
});
32+
33+
it("kill-switch (#2339): a global kill-switch blocks unconditionally, even with every counter otherwise clear", () => {
34+
expect(shouldReenter(baseCandidate({ killSwitchScope: "global" }))).toEqual({
35+
reenter: false,
36+
reasons: ["global_kill_switch_active"],
37+
});
38+
});
39+
40+
it("kill-switch (#2339): a per-repo kill-switch blocks unconditionally, checked before the circuit breaker or rate caps", () => {
41+
expect(
42+
shouldReenter(baseCandidate({ killSwitchScope: "repo", outcome: "disengaged", consecutiveDisengagements: 99 })),
43+
).toEqual({ reenter: false, reasons: ["repo_kill_switch_active"] });
44+
});
45+
46+
it("kill-switch (#2339): an inactive kill-switch (scope 'none') never itself blocks -- other checks are still evaluated normally", () => {
47+
expect(shouldReenter(baseCandidate({ killSwitchScope: "none" })).reenter).toBe(true);
48+
});
49+
50+
it("an 'other' outcome (neither merged nor disengaged) is never subject to the per-repo circuit breaker", () => {
51+
expect(shouldReenter(baseCandidate({ outcome: "other" }))).toEqual({ reenter: true, reasons: [] });
52+
});
53+
54+
it("circuit breaker: a disengaged outcome at or beyond the consecutive-disengagement ceiling pauses the repo", () => {
55+
const decision = shouldReenter(
56+
baseCandidate({ outcome: "disengaged", consecutiveDisengagements: 3, maxConsecutiveDisengagements: 3 }),
57+
);
58+
expect(decision.reenter).toBe(false);
59+
expect(decision.reasons).toEqual(["repo_paused_after_consecutive_disengagements:3>=3"]);
60+
});
61+
62+
it("circuit breaker: a disengaged outcome below the ceiling still re-enters", () => {
63+
expect(
64+
shouldReenter(baseCandidate({ outcome: "disengaged", consecutiveDisengagements: 2, maxConsecutiveDisengagements: 3 }))
65+
.reenter,
66+
).toBe(true);
67+
});
68+
69+
it("circuit breaker: a HIGH consecutiveDisengagements count never pauses a repo whose outcome ISN'T disengaged", () => {
70+
// Exercises the && short-circuit's left-false side distinctly from the right-side threshold check -- a
71+
// repo could have a high historical tally but just landed a merge, which must not be treated as a pause.
72+
expect(
73+
shouldReenter(baseCandidate({ outcome: "merged", consecutiveDisengagements: 99, maxConsecutiveDisengagements: 3 }))
74+
.reenter,
75+
).toBe(true);
76+
});
77+
78+
it("rate cap: an hourly re-entry ceiling at or beyond the limit blocks, independent of repo history", () => {
79+
// baseCandidate defaults to outcome:"merged" — a healthy merge must still respect a spent hourly cap.
80+
const decision = shouldReenter(baseCandidate({ reentriesThisHour: 4, maxReentriesPerHour: 4 }));
81+
expect(decision.reenter).toBe(false);
82+
expect(decision.reasons).toEqual(["hourly_reentry_cap_reached:4>=4"]);
83+
});
84+
85+
it("rate cap: an hourly count below the limit does not block", () => {
86+
expect(shouldReenter(baseCandidate({ reentriesThisHour: 3, maxReentriesPerHour: 4 })).reenter).toBe(true);
87+
});
88+
89+
it("rate cap: a session re-entry ceiling at or beyond the limit blocks, independent of the hourly cap", () => {
90+
const decision = shouldReenter(baseCandidate({ reentriesThisSession: 20, maxReentriesPerSession: 20 }));
91+
expect(decision.reenter).toBe(false);
92+
expect(decision.reasons).toEqual(["session_reentry_cap_reached:20>=20"]);
93+
});
94+
95+
it("rate cap: a session count below the limit does not block", () => {
96+
expect(shouldReenter(baseCandidate({ reentriesThisSession: 19, maxReentriesPerSession: 20 })).reenter).toBe(true);
97+
});
98+
99+
it("every ceiling that is exceeded is reported, not just the first one checked", () => {
100+
const decision = shouldReenter(
101+
baseCandidate({
102+
outcome: "disengaged",
103+
consecutiveDisengagements: 5,
104+
maxConsecutiveDisengagements: 3,
105+
reentriesThisHour: 10,
106+
maxReentriesPerHour: 4,
107+
reentriesThisSession: 30,
108+
maxReentriesPerSession: 20,
109+
}),
110+
);
111+
expect(decision.reenter).toBe(false);
112+
expect(decision.reasons).toHaveLength(3);
113+
});
114+
115+
it("default thresholds apply when the candidate omits its own overrides", () => {
116+
const justUnderDefault = shouldReenter(
117+
baseCandidate({ outcome: "disengaged", consecutiveDisengagements: DEFAULT_MAX_CONSECUTIVE_DISENGAGEMENTS - 1 }),
118+
);
119+
expect(justUnderDefault.reenter).toBe(true);
120+
121+
const atDefault = shouldReenter(
122+
baseCandidate({ outcome: "disengaged", consecutiveDisengagements: DEFAULT_MAX_CONSECUTIVE_DISENGAGEMENTS }),
123+
);
124+
expect(atDefault.reenter).toBe(false);
125+
126+
expect(shouldReenter(baseCandidate({ reentriesThisHour: DEFAULT_MAX_REENTRIES_PER_HOUR })).reenter).toBe(false);
127+
expect(shouldReenter(baseCandidate({ reentriesThisSession: DEFAULT_MAX_REENTRIES_PER_SESSION })).reenter).toBe(false);
128+
});
129+
130+
it("a caller-supplied threshold overrides the default rather than being ignored", () => {
131+
// A count that would pass under the DEFAULT ceiling must still block under a stricter caller override.
132+
expect(
133+
shouldReenter(baseCandidate({ outcome: "disengaged", consecutiveDisengagements: 1, maxConsecutiveDisengagements: 1 }))
134+
.reenter,
135+
).toBe(false);
136+
});
137+
138+
it("caller-supplied hourly and session thresholds override the defaults", () => {
139+
expect(shouldReenter(baseCandidate({ reentriesThisHour: 1, maxReentriesPerHour: 1 })).reenter).toBe(false);
140+
expect(shouldReenter(baseCandidate({ reentriesThisSession: 1, maxReentriesPerSession: 1 })).reenter).toBe(false);
141+
});
142+
});

0 commit comments

Comments
 (0)