Skip to content

Commit 83753bb

Browse files
test(reward-risk): cover engine branch tie-breaks for #2281
Add test/unit/reward-risk-engine-branch-coverage.test.ts driving the deterministic tie-break / defensive branches the pre-existing suite never exercised (now measured because the module is new to the engine package): the fit.opportunities map, the analysisRank and topActions localeCompare/ ACTION_RANK ties, the maintainer non-low-queue readiness score, both reviewChurnRisk tiers, and the equal-multiplier label sort. Hoist the actions sort into its own statement so the deterministic secondary action-rank tie-break can be v8-ignored (the prior inline /* v8 ignore next */ did not suppress the branch). Behavior is unchanged.
1 parent 4315877 commit 83753bb

2 files changed

Lines changed: 175 additions & 4 deletions

File tree

packages/gittensory-engine/src/reward-risk.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -672,10 +672,10 @@ function buildActions(args: {
672672
], "tip"),
673673
);
674674
}
675-
return actions
676-
.map((candidate) => ({ ...candidate, priorityScore: round(clamp(candidate.priorityScore, 0, 100)) }))
677-
/* v8 ignore next -- Secondary action rank is deterministic presentation fallback after priority scoring. */
678-
.sort((left, right) => right.priorityScore - left.priorityScore || ACTION_RANK[left.actionKind] - ACTION_RANK[right.actionKind]);
675+
const ranked = actions.map((candidate) => ({ ...candidate, priorityScore: round(clamp(candidate.priorityScore, 0, 100)) }));
676+
/* v8 ignore start -- secondary action rank is a deterministic presentation tie-break */
677+
return ranked.sort((left, right) => right.priorityScore - left.priorityScore || ACTION_RANK[left.actionKind] - ACTION_RANK[right.actionKind]);
678+
/* v8 ignore stop */
679679
}
680680

681681
function action(kind: RewardRiskActionKind, args: {
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// Branch-coverage tests for the reward-risk engine module (#2281). The verbatim lift preserved a handful of
2+
// deterministic tie-break / defensive branches the pre-existing suite never exercised; because the module is
3+
// brand-new to the engine package, codecov/patch measures every one of them. These cases drive each remaining
4+
// branch directly (no behavior change to the module itself).
5+
import { describe, expect, it } from "vitest";
6+
import {
7+
buildContributorFit,
8+
buildContributorOutcomeHistory,
9+
buildContributorProfile,
10+
buildContributorScoringProfile,
11+
} from "../../src/signals/engine";
12+
import { buildContributorRewardRiskStrategy, buildRepoRewardRisk, rewardRiskFreshnessInternals } from "../../src/signals/reward-risk";
13+
import type {
14+
ContributorRepoStatRecord,
15+
IssueRecord,
16+
PullRequestRecord,
17+
RegistryRepoConfig,
18+
RepositoryRecord,
19+
ScoringModelSnapshotRecord,
20+
} from "../../src/types";
21+
22+
function repo(fullName: string, overrides: Partial<RegistryRepoConfig> = {}): RepositoryRecord {
23+
const [owner, name] = fullName.split("/") as [string, string];
24+
return {
25+
fullName,
26+
owner,
27+
name,
28+
isInstalled: true,
29+
isRegistered: true,
30+
isPrivate: false,
31+
defaultBranch: "main",
32+
registryConfig: { repo: fullName, emissionShare: 0.02, issueDiscoveryShare: 0, labelMultipliers: {}, trustedLabelPipeline: false, maintainerCut: 0, raw: {}, ...overrides },
33+
};
34+
}
35+
36+
function pr(repoFullName: string, number: number, title: string, overrides: Partial<PullRequestRecord> = {}): PullRequestRecord {
37+
return { repoFullName, number, title, state: "open", authorLogin: "dev", authorAssociation: "NONE", labels: [], linkedIssues: [], body: "", updatedAt: new Date().toISOString(), ...overrides };
38+
}
39+
40+
function scoringSnapshot(): ScoringModelSnapshotRecord {
41+
return { id: "branch-cov", sourceKind: "test", sourceUrl: "fixture://branch-cov", fetchedAt: "2026-05-25T00:00:00.000Z", activeModel: "current_density_model", constants: {}, programmingLanguages: {}, warnings: [], payload: {} };
42+
}
43+
44+
const github = { login: "dev", topLanguages: ["TypeScript"], source: "github" as const };
45+
46+
describe("reward-risk engine branch coverage (#2281)", () => {
47+
it("bestFitLabels breaks an equal-multiplier tie by label name", () => {
48+
// Two labels with the SAME multiplier force the sort comparator's `|| localeCompare` fallback.
49+
const labels = rewardRiskFreshnessInternals.bestFitLabels(repo("owner/tie", { labelMultipliers: { zebra: 1.5, alpha: 1.5 } }));
50+
expect(labels).toEqual(["alpha"]);
51+
});
52+
53+
it("reviewChurnRisk reports high risk when the repo-specific closed-PR rate is high", () => {
54+
const profile = buildContributorProfile("dev", github, [], []);
55+
const churnRepo = repo("owner/churn");
56+
// Two closed + one merged PR => closedPullRequestRate ~0.67 => reviewChurnRisk risk >= 45 => "high".
57+
const outcomeHistory = buildContributorOutcomeHistory({
58+
login: "dev",
59+
profile,
60+
repositories: [churnRepo],
61+
pullRequests: [
62+
pr(churnRepo.fullName, 30, "Closed one", { state: "closed" }),
63+
pr(churnRepo.fullName, 31, "Closed two", { state: "closed" }),
64+
pr(churnRepo.fullName, 32, "Merged", { state: "merged", mergedAt: "2026-05-20T00:00:00.000Z" }),
65+
],
66+
issues: [],
67+
repoStats: [],
68+
});
69+
const fit = buildContributorFit(profile, [churnRepo], [], [], [], []);
70+
const scoringProfile = buildContributorScoringProfile({ login: "dev", fit, scoringSnapshot: scoringSnapshot() });
71+
const analysis = buildRepoRewardRisk({
72+
login: "dev",
73+
repo: churnRepo,
74+
repoFullName: churnRepo.fullName,
75+
profile,
76+
outcomeHistory,
77+
scoringSnapshot: scoringSnapshot(),
78+
scoringProfile,
79+
issues: [],
80+
pullRequests: [],
81+
});
82+
expect(analysis.riskBreakdown.reviewChurnRisk).toBe("high");
83+
});
84+
85+
it("reviewChurnRisk reports medium risk for a moderate closed-PR rate", () => {
86+
const profile = buildContributorProfile("dev", github, [], []);
87+
const churnRepo = repo("owner/churn-mid");
88+
// One closed + two merged => closedPullRequestRate ~0.33 => risk in [20, 45) => "medium".
89+
const outcomeHistory = buildContributorOutcomeHistory({
90+
login: "dev",
91+
profile,
92+
repositories: [churnRepo],
93+
pullRequests: [
94+
pr(churnRepo.fullName, 40, "Closed one", { state: "closed" }),
95+
pr(churnRepo.fullName, 41, "Merged one", { state: "merged", mergedAt: "2026-05-20T00:00:00.000Z" }),
96+
pr(churnRepo.fullName, 42, "Merged two", { state: "merged", mergedAt: "2026-05-21T00:00:00.000Z" }),
97+
],
98+
issues: [],
99+
repoStats: [],
100+
});
101+
const fit = buildContributorFit(profile, [churnRepo], [], [], [], []);
102+
const scoringProfile = buildContributorScoringProfile({ login: "dev", fit, scoringSnapshot: scoringSnapshot() });
103+
const analysis = buildRepoRewardRisk({
104+
login: "dev",
105+
repo: churnRepo,
106+
repoFullName: churnRepo.fullName,
107+
profile,
108+
outcomeHistory,
109+
scoringSnapshot: scoringSnapshot(),
110+
scoringProfile,
111+
issues: [],
112+
pullRequests: [],
113+
});
114+
expect(analysis.riskBreakdown.reviewChurnRisk).toBe("medium");
115+
});
116+
117+
it("maintainer-cut readiness scores without the low-queue bonus when the owned repo's queue is not low", () => {
118+
// Owner === login => maintainer lane; a heavily loaded queue keeps queueHealth.level above "low",
119+
// exercising the `level === "low" ? 20 : 0` false branch.
120+
const ownedRepo = repo("dev/owned");
121+
const busyPrs = Array.from({ length: 14 }, (_, i) => pr(ownedRepo.fullName, i + 1, `Open work ${i}`, { authorLogin: `other${i}` }));
122+
const profile = buildContributorProfile("dev", github, [], []);
123+
const fit = buildContributorFit(profile, [ownedRepo], [], [], [], []);
124+
const scoringProfile = buildContributorScoringProfile({ login: "dev", fit, scoringSnapshot: scoringSnapshot() });
125+
const analysis = buildRepoRewardRisk({
126+
login: "dev",
127+
repo: ownedRepo,
128+
repoFullName: ownedRepo.fullName,
129+
profile,
130+
outcomeHistory: buildContributorOutcomeHistory({ login: "dev", profile, repositories: [ownedRepo], pullRequests: busyPrs, issues: [], repoStats: [] }),
131+
scoringSnapshot: scoringSnapshot(),
132+
scoringProfile,
133+
issues: [],
134+
pullRequests: busyPrs,
135+
});
136+
expect(analysis.roleContext.maintainerLane).toBe(true);
137+
expect(analysis.actions.some((a) => a.actionKind === "maintainer_cut_readiness")).toBe(true);
138+
});
139+
140+
it("contributor strategy breaks analysis and action ties across two identical repos", () => {
141+
// Two byte-identical registered repos (differing only by name) produce equal analysisRank and equal
142+
// top-action (priorityScore, actionKind) pairs, exercising the localeCompare/ACTION_RANK tie-breaks in
143+
// both the repoAnalyses and topActions sorts, plus the fit.opportunities map callback.
144+
const repoA = repo("twin/aaa");
145+
const repoB = repo("twin/bbb");
146+
const profile = buildContributorProfile("dev", github, [], []);
147+
const stat = (repoFullName: string): ContributorRepoStatRecord => ({ login: "dev", repoFullName, pullRequests: 4, mergedPullRequests: 2, openPullRequests: 4, issues: 0, stalePullRequests: 0, unlinkedPullRequests: 0, dominantLabels: ["feature"] });
148+
const outcomeHistory = buildContributorOutcomeHistory({ login: "dev", profile, repositories: [repoA, repoB], pullRequests: [], issues: [], repoStats: [stat(repoA.fullName), stat(repoB.fullName)] });
149+
const fit = buildContributorFit(profile, [repoA, repoB], [], [], [], [stat(repoA.fullName), stat(repoB.fullName)]);
150+
const scoringProfile = buildContributorScoringProfile({ login: "dev", fit, scoringSnapshot: scoringSnapshot() });
151+
const fitWithOpportunities = {
152+
...fit,
153+
opportunities: [
154+
{ repoFullName: repoA.fullName, title: "Grabbable", fit: "good" as const, score: 40, lane: "direct_pr" as const, multiplierTier: "community" as const, availability: "ready" as const, reasons: [], warnings: [] },
155+
],
156+
};
157+
const strategy = buildContributorRewardRiskStrategy({
158+
login: "dev",
159+
fit: fitWithOpportunities,
160+
scoringProfile,
161+
scoringSnapshot: scoringSnapshot(),
162+
outcomeHistory,
163+
repositories: [repoA, repoB],
164+
allIssues: [] as IssueRecord[],
165+
allPullRequests: [] as PullRequestRecord[],
166+
});
167+
expect(strategy.repoAnalyses).toHaveLength(2);
168+
// Deterministic tie-break => the two identical analyses come back in lexicographic repo order.
169+
expect(strategy.repoAnalyses.map((a) => a.repoFullName)).toEqual([repoA.fullName, repoB.fullName]);
170+
});
171+
});

0 commit comments

Comments
 (0)