Skip to content

Commit 112ff05

Browse files
committed
test(calibration): make signal-tracking's real coverage visible to Codecov
`packages/loopover-engine/src/calibration/signal-tracking.ts` (#7982) is fully exercised by the engine package's own `node --test` suite, but that runner is not part of the root vitest run Codecov reads `codecov/patch` from, so `computeRulePrecision` / `computeRuleRepeatCount` / `evaluateRuleRepeatAlarm` report as ~0% covered despite being genuinely tested (same blind spot as #6250). Add a root-level vitest twin that imports the three primitives via the engine barrel and re-exercises every scenario the package suite covers, so vitest — and therefore Codecov — sees the module too. Mirrors the existing sibling twins `test/unit/calibration-dashboard.test.ts` and `test/unit/discovery-soft-claim.test.ts`. Test-only: no change to any file under `packages/loopover-engine/src/**` or `packages/loopover-engine/test/**`. Covers both arms of every branch in the three functions (100% line + branch of the source module locally). Closes #8343
1 parent af270b9 commit 112ff05

1 file changed

Lines changed: 165 additions & 0 deletions

File tree

test/unit/signal-tracking.test.ts

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Root-level vitest coverage twin for `packages/loopover-engine/src/calibration/signal-tracking.ts` (#7982).
2+
//
3+
// The engine package already has a full, passing `node --test` suite at
4+
// `packages/loopover-engine/test/signal-tracking.test.ts`, but that runner is NOT part of the root vitest run
5+
// Codecov reads `codecov/patch` from — so `computeRulePrecision`/`computeRuleRepeatCount`/
6+
// `evaluateRuleRepeatAlarm` report as ~0% covered despite being genuinely tested (#8343, same blind spot as
7+
// #6250). This file re-exercises the same behavior through the engine barrel so vitest (and therefore Codecov)
8+
// sees it too, exactly like the sibling twins `test/unit/calibration-dashboard.test.ts` and
9+
// `test/unit/discovery-soft-claim.test.ts`. It intentionally mirrors every scenario in the package's own suite
10+
// and covers both arms of every branch in the three functions.
11+
import { describe, expect, it } from "vitest";
12+
import {
13+
computeRulePrecision,
14+
computeRuleRepeatCount,
15+
evaluateRuleRepeatAlarm,
16+
} from "../../packages/loopover-engine/src/index";
17+
import type { HumanOverrideEvent, RuleFiredEvent } from "../../packages/loopover-engine/src/index";
18+
19+
function fired(ruleId: string, targetKey: string, overrides: Partial<RuleFiredEvent> = {}): RuleFiredEvent {
20+
return { ruleId, targetKey, outcome: "block", occurredAt: "2026-07-22T00:00:00.000Z", ...overrides };
21+
}
22+
23+
function override(
24+
ruleId: string,
25+
targetKey: string,
26+
verdict: HumanOverrideEvent["verdict"],
27+
overrides: Partial<HumanOverrideEvent> = {},
28+
): HumanOverrideEvent {
29+
return { ruleId, targetKey, verdict, occurredAt: "2026-07-22T00:00:00.000Z", ...overrides };
30+
}
31+
32+
describe("barrel: signal-tracking primitives are re-exported from the engine entrypoint (#7982/#7983)", () => {
33+
it("exposes the three primitives as functions", () => {
34+
expect(typeof computeRulePrecision).toBe("function");
35+
expect(typeof computeRuleRepeatCount).toBe("function");
36+
expect(typeof evaluateRuleRepeatAlarm).toBe("function");
37+
});
38+
});
39+
40+
describe("computeRulePrecision", () => {
41+
it("no overrides -> decided is 0 and precision is null (unknown stays unknown, never coerced)", () => {
42+
const report = computeRulePrecision(
43+
"missing_linked_issue",
44+
[fired("missing_linked_issue", "a#1"), fired("missing_linked_issue", "a#2")],
45+
[],
46+
);
47+
expect(report).toEqual({
48+
ruleId: "missing_linked_issue",
49+
fired: 2,
50+
reversed: 0,
51+
confirmed: 0,
52+
decided: 0,
53+
precision: null,
54+
});
55+
});
56+
57+
it("mixes confirmed and reversed verdicts into a real precision (decided > 0 arm)", () => {
58+
const report = computeRulePrecision(
59+
"missing_linked_issue",
60+
[
61+
fired("missing_linked_issue", "a#1"),
62+
fired("missing_linked_issue", "a#2"),
63+
fired("missing_linked_issue", "a#3"),
64+
],
65+
[
66+
override("missing_linked_issue", "a#1", "confirmed"),
67+
override("missing_linked_issue", "a#2", "confirmed"),
68+
override("missing_linked_issue", "a#3", "reversed"),
69+
],
70+
);
71+
expect(report.fired).toBe(3);
72+
expect(report.confirmed).toBe(2);
73+
expect(report.reversed).toBe(1);
74+
expect(report.decided).toBe(3);
75+
expect(report.precision).toBe(2 / 3);
76+
});
77+
78+
it("100% reversed yields precision 0, not null (a real, scored bad outcome, not an unknown one)", () => {
79+
const report = computeRulePrecision("bad_rule", [fired("bad_rule", "a#1")], [override("bad_rule", "a#1", "reversed")]);
80+
expect(report.decided).toBe(1);
81+
expect(report.precision).toBe(0);
82+
});
83+
84+
it("ignores fired/override events for a DIFFERENT ruleId entirely (both operand branches)", () => {
85+
const report = computeRulePrecision(
86+
"rule_a",
87+
[fired("rule_a", "a#1"), fired("rule_b", "a#2")],
88+
[override("rule_a", "a#1", "confirmed"), override("rule_b", "a#2", "reversed")],
89+
);
90+
expect(report.fired).toBe(1);
91+
expect(report.confirmed).toBe(1);
92+
expect(report.reversed).toBe(0);
93+
});
94+
95+
it("an override with no matching fired event still counts toward decided (no cross-validation between the two lists)", () => {
96+
const report = computeRulePrecision("rule_a", [], [override("rule_a", "a#1", "confirmed")]);
97+
expect(report.fired).toBe(0);
98+
expect(report.decided).toBe(1);
99+
expect(report.precision).toBe(1);
100+
});
101+
});
102+
103+
describe("computeRuleRepeatCount", () => {
104+
it("counts only fires matching BOTH ruleId and targetKey (each && operand's false arm exercised)", () => {
105+
const events = [
106+
fired("rule_a", "a#1"),
107+
fired("rule_a", "a#1"),
108+
fired("rule_a", "a#2"), // same rule, different target -> targetKey operand false
109+
fired("rule_b", "a#1"), // different rule, same target -> ruleId operand false
110+
];
111+
expect(computeRuleRepeatCount("rule_a", "a#1", events)).toBe(2);
112+
expect(computeRuleRepeatCount("rule_a", "a#2", events)).toBe(1);
113+
expect(computeRuleRepeatCount("rule_b", "a#1", events)).toBe(1);
114+
expect(computeRuleRepeatCount("rule_a", "a#3", events)).toBe(0);
115+
});
116+
117+
it("zero fired events yields 0, not an error", () => {
118+
expect(computeRuleRepeatCount("rule_a", "a#1", [])).toBe(0);
119+
});
120+
});
121+
122+
describe("evaluateRuleRepeatAlarm (#7983)", () => {
123+
it("not triggered below the threshold", () => {
124+
const verdict = evaluateRuleRepeatAlarm("rule_a", [fired("rule_a", "a#1"), fired("rule_a", "a#2")], 3);
125+
expect(verdict.triggered).toBe(false);
126+
expect(verdict.affectedTargets).toEqual(["a#1", "a#2"]);
127+
expect(verdict.threshold).toBe(3);
128+
});
129+
130+
it("triggers once distinct targets reach the threshold (boundary: exactly `threshold` distinct targets)", () => {
131+
const verdict = evaluateRuleRepeatAlarm(
132+
"rule_a",
133+
[fired("rule_a", "a#1"), fired("rule_a", "a#2"), fired("rule_a", "a#3")],
134+
3,
135+
);
136+
expect(verdict.triggered).toBe(true);
137+
expect(verdict.affectedTargets).toEqual(["a#1", "a#2", "a#3"]);
138+
});
139+
140+
it("the SAME target firing repeatedly counts once, deduplicated in first-seen order (seen.has arm)", () => {
141+
const verdict = evaluateRuleRepeatAlarm(
142+
"rule_a",
143+
[fired("rule_a", "a#1"), fired("rule_a", "a#1"), fired("rule_a", "a#1")],
144+
2,
145+
);
146+
expect(verdict.affectedTargets).toEqual(["a#1"]);
147+
expect(verdict.triggered).toBe(false);
148+
});
149+
150+
it("ignores fired events for a DIFFERENT ruleId entirely (ruleId mismatch arm)", () => {
151+
const verdict = evaluateRuleRepeatAlarm(
152+
"rule_a",
153+
[fired("rule_a", "a#1"), fired("rule_b", "a#2"), fired("rule_b", "a#3")],
154+
2,
155+
);
156+
expect(verdict.affectedTargets).toEqual(["a#1"]);
157+
expect(verdict.triggered).toBe(false);
158+
});
159+
160+
it("zero fired events never triggers", () => {
161+
const verdict = evaluateRuleRepeatAlarm("rule_a", [], 1);
162+
expect(verdict.triggered).toBe(false);
163+
expect(verdict.affectedTargets).toEqual([]);
164+
});
165+
});

0 commit comments

Comments
 (0)