diff --git a/test/unit/reward-risk-engine-branch-coverage.test.ts b/test/unit/reward-risk-engine-branch-coverage.test.ts deleted file mode 100644 index 74053b86f7..0000000000 --- a/test/unit/reward-risk-engine-branch-coverage.test.ts +++ /dev/null @@ -1,171 +0,0 @@ -// Branch-coverage tests for the reward-risk engine module (#2281). The verbatim lift preserved a handful of -// deterministic tie-break / defensive branches the pre-existing suite never exercised; because the module is -// brand-new to the engine package, codecov/patch measures every one of them. These cases drive each remaining -// branch directly (no behavior change to the module itself). -import { describe, expect, it } from "vitest"; -import { - buildContributorFit, - buildContributorOutcomeHistory, - buildContributorProfile, - buildContributorScoringProfile, -} from "../../src/signals/engine"; -import { buildContributorRewardRiskStrategy, buildRepoRewardRisk, rewardRiskFreshnessInternals } from "../../src/signals/reward-risk"; -import type { - ContributorRepoStatRecord, - IssueRecord, - PullRequestRecord, - RegistryRepoConfig, - RepositoryRecord, - ScoringModelSnapshotRecord, -} from "../../src/types"; - -function repo(fullName: string, overrides: Partial = {}): RepositoryRecord { - const [owner, name] = fullName.split("/") as [string, string]; - return { - fullName, - owner, - name, - isInstalled: true, - isRegistered: true, - isPrivate: false, - defaultBranch: "main", - registryConfig: { repo: fullName, emissionShare: 0.02, issueDiscoveryShare: 0, labelMultipliers: {}, trustedLabelPipeline: false, maintainerCut: 0, raw: {}, ...overrides }, - }; -} - -function pr(repoFullName: string, number: number, title: string, overrides: Partial = {}): PullRequestRecord { - return { repoFullName, number, title, state: "open", authorLogin: "dev", authorAssociation: "NONE", labels: [], linkedIssues: [], body: "", updatedAt: new Date().toISOString(), ...overrides }; -} - -function scoringSnapshot(): ScoringModelSnapshotRecord { - 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: {} }; -} - -const github = { login: "dev", topLanguages: ["TypeScript"], source: "github" as const }; - -describe("reward-risk engine branch coverage (#2281)", () => { - it("bestFitLabels breaks an equal-multiplier tie by label name", () => { - // Two labels with the SAME multiplier force the sort comparator's `|| localeCompare` fallback. - const labels = rewardRiskFreshnessInternals.bestFitLabels(repo("owner/tie", { labelMultipliers: { zebra: 1.5, alpha: 1.5 } })); - expect(labels).toEqual(["alpha"]); - }); - - it("reviewChurnRisk reports high risk when the repo-specific closed-PR rate is high", () => { - const profile = buildContributorProfile("dev", github, [], []); - const churnRepo = repo("owner/churn"); - // Two closed + one merged PR => closedPullRequestRate ~0.67 => reviewChurnRisk risk >= 45 => "high". - const outcomeHistory = buildContributorOutcomeHistory({ - login: "dev", - profile, - repositories: [churnRepo], - pullRequests: [ - pr(churnRepo.fullName, 30, "Closed one", { state: "closed" }), - pr(churnRepo.fullName, 31, "Closed two", { state: "closed" }), - pr(churnRepo.fullName, 32, "Merged", { state: "merged", mergedAt: "2026-05-20T00:00:00.000Z" }), - ], - issues: [], - repoStats: [], - }); - const fit = buildContributorFit(profile, [churnRepo], [], [], [], []); - const scoringProfile = buildContributorScoringProfile({ login: "dev", fit, scoringSnapshot: scoringSnapshot() }); - const analysis = buildRepoRewardRisk({ - login: "dev", - repo: churnRepo, - repoFullName: churnRepo.fullName, - profile, - outcomeHistory, - scoringSnapshot: scoringSnapshot(), - scoringProfile, - issues: [], - pullRequests: [], - }); - expect(analysis.riskBreakdown.reviewChurnRisk).toBe("high"); - }); - - it("reviewChurnRisk reports medium risk for a moderate closed-PR rate", () => { - const profile = buildContributorProfile("dev", github, [], []); - const churnRepo = repo("owner/churn-mid"); - // One closed + two merged => closedPullRequestRate ~0.33 => risk in [20, 45) => "medium". - const outcomeHistory = buildContributorOutcomeHistory({ - login: "dev", - profile, - repositories: [churnRepo], - pullRequests: [ - pr(churnRepo.fullName, 40, "Closed one", { state: "closed" }), - pr(churnRepo.fullName, 41, "Merged one", { state: "merged", mergedAt: "2026-05-20T00:00:00.000Z" }), - pr(churnRepo.fullName, 42, "Merged two", { state: "merged", mergedAt: "2026-05-21T00:00:00.000Z" }), - ], - issues: [], - repoStats: [], - }); - const fit = buildContributorFit(profile, [churnRepo], [], [], [], []); - const scoringProfile = buildContributorScoringProfile({ login: "dev", fit, scoringSnapshot: scoringSnapshot() }); - const analysis = buildRepoRewardRisk({ - login: "dev", - repo: churnRepo, - repoFullName: churnRepo.fullName, - profile, - outcomeHistory, - scoringSnapshot: scoringSnapshot(), - scoringProfile, - issues: [], - pullRequests: [], - }); - expect(analysis.riskBreakdown.reviewChurnRisk).toBe("medium"); - }); - - it("maintainer-cut readiness scores without the low-queue bonus when the owned repo's queue is not low", () => { - // Owner === login => maintainer lane; a heavily loaded queue keeps queueHealth.level above "low", - // exercising the `level === "low" ? 20 : 0` false branch. - const ownedRepo = repo("dev/owned"); - const busyPrs = Array.from({ length: 14 }, (_, i) => pr(ownedRepo.fullName, i + 1, `Open work ${i}`, { authorLogin: `other${i}` })); - const profile = buildContributorProfile("dev", github, [], []); - const fit = buildContributorFit(profile, [ownedRepo], [], [], [], []); - const scoringProfile = buildContributorScoringProfile({ login: "dev", fit, scoringSnapshot: scoringSnapshot() }); - const analysis = buildRepoRewardRisk({ - login: "dev", - repo: ownedRepo, - repoFullName: ownedRepo.fullName, - profile, - outcomeHistory: buildContributorOutcomeHistory({ login: "dev", profile, repositories: [ownedRepo], pullRequests: busyPrs, issues: [], repoStats: [] }), - scoringSnapshot: scoringSnapshot(), - scoringProfile, - issues: [], - pullRequests: busyPrs, - }); - expect(analysis.roleContext.maintainerLane).toBe(true); - expect(analysis.actions.some((a) => a.actionKind === "maintainer_cut_readiness")).toBe(true); - }); - - it("contributor strategy breaks analysis and action ties across two identical repos", () => { - // Two byte-identical registered repos (differing only by name) produce equal analysisRank and equal - // top-action (priorityScore, actionKind) pairs, exercising the localeCompare/ACTION_RANK tie-breaks in - // both the repoAnalyses and topActions sorts, plus the fit.opportunities map callback. - const repoA = repo("twin/aaa"); - const repoB = repo("twin/bbb"); - const profile = buildContributorProfile("dev", github, [], []); - const stat = (repoFullName: string): ContributorRepoStatRecord => ({ login: "dev", repoFullName, pullRequests: 4, mergedPullRequests: 2, openPullRequests: 4, issues: 0, stalePullRequests: 0, unlinkedPullRequests: 0, dominantLabels: ["feature"] }); - const outcomeHistory = buildContributorOutcomeHistory({ login: "dev", profile, repositories: [repoA, repoB], pullRequests: [], issues: [], repoStats: [stat(repoA.fullName), stat(repoB.fullName)] }); - const fit = buildContributorFit(profile, [repoA, repoB], [], [], [], [stat(repoA.fullName), stat(repoB.fullName)]); - const scoringProfile = buildContributorScoringProfile({ login: "dev", fit, scoringSnapshot: scoringSnapshot() }); - const fitWithOpportunities = { - ...fit, - opportunities: [ - { 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: [] }, - ], - }; - const strategy = buildContributorRewardRiskStrategy({ - login: "dev", - fit: fitWithOpportunities, - scoringProfile, - scoringSnapshot: scoringSnapshot(), - outcomeHistory, - repositories: [repoA, repoB], - allIssues: [] as IssueRecord[], - allPullRequests: [] as PullRequestRecord[], - }); - expect(strategy.repoAnalyses).toHaveLength(2); - // Deterministic tie-break => the two identical analyses come back in lexicographic repo order. - expect(strategy.repoAnalyses.map((a) => a.repoFullName)).toEqual([repoA.fullName, repoB.fullName]); - }); -}); diff --git a/test/unit/reward-risk-freshness.test.ts b/test/unit/reward-risk-freshness.test.ts index cc110c4fb9..4e91fcc723 100644 --- a/test/unit/reward-risk-freshness.test.ts +++ b/test/unit/reward-risk-freshness.test.ts @@ -4,11 +4,13 @@ import { type FreshnessIssue, } from "../../packages/loopover-engine/src/opportunity-freshness"; import { + buildContributorFit, buildContributorOutcomeHistory, buildContributorProfile, + buildContributorScoringProfile, } from "../../src/signals/engine"; import { buildRepoRewardRisk, rewardRiskFreshnessInternals } from "../../src/signals/reward-risk"; -import type { IssueRecord, RepositoryRecord, ScoringModelSnapshotRecord } from "../../src/types"; +import type { IssueRecord, PullRequestRecord, RepositoryRecord, ScoringModelSnapshotRecord } from "../../src/types"; function scoringSnapshot(): ScoringModelSnapshotRecord { return { @@ -71,6 +73,22 @@ function toFreshnessIssues(issues: IssueRecord[]): FreshnessIssue[] { })); } +function pr(repoFullName: string, number: number, title: string, overrides: Partial = {}): PullRequestRecord { + return { + repoFullName, + number, + title, + state: "open", + authorLogin: "dev", + authorAssociation: "NONE", + labels: [], + linkedIssues: [], + body: "", + updatedAt: new Date().toISOString(), + ...overrides, + }; +} + describe("reward-risk freshness parity with loopover-engine", () => { const collab = repo("owner/collab-repo"); const profile = buildContributorProfile("dev", { login: "dev", topLanguages: [], source: "github" }, [], []); @@ -174,6 +192,11 @@ describe("bestFitLabels keyword anchoring", () => { expect(rewardRiskFreshnessInternals.bestFitLabels(null)).toEqual([]); }); + it("breaks an equal-multiplier tie by label name", () => { + // Two labels with the SAME multiplier force the sort comparator's `|| localeCompare` fallback. + expect(pick({ zebra: 1.5, alpha: 1.5 })).toEqual(["alpha"]); + }); + it("aligns its meta-label exclusion set to engine.ts's canonical suspicious-label matcher (#7251)", () => { // Keywords the canonical audit excludes that the old divergent copy MISSED are now excluded here too. for (const key of ["state", "bot", "loopover", "reward", "score", "miner"]) { @@ -184,3 +207,92 @@ describe("bestFitLabels keyword anchoring", () => { expect(pick({ "contributor:top-tier": 5, bug: 2 })).toEqual(["contributor:top-tier"]); }); }); + +describe("buildRepoRewardRisk — reviewChurnRisk and maintainer-cut readiness branch coverage (#2281)", () => { + it("reviewChurnRisk reports high risk when the repo-specific closed-PR rate is high", () => { + const profile = buildContributorProfile("dev", { login: "dev", topLanguages: ["TypeScript"], source: "github" }, [], []); + const churnRepo = repo("owner/churn"); + // Two closed + one merged PR => closedPullRequestRate ~0.67 => reviewChurnRisk risk >= 45 => "high". + const outcomeHistory = buildContributorOutcomeHistory({ + login: "dev", + profile, + repositories: [churnRepo], + pullRequests: [ + pr(churnRepo.fullName, 30, "Closed one", { state: "closed" }), + pr(churnRepo.fullName, 31, "Closed two", { state: "closed" }), + pr(churnRepo.fullName, 32, "Merged", { state: "merged", mergedAt: "2026-05-20T00:00:00.000Z" }), + ], + issues: [], + repoStats: [], + }); + const fit = buildContributorFit(profile, [churnRepo], [], [], [], []); + const scoringProfile = buildContributorScoringProfile({ login: "dev", fit, scoringSnapshot: scoringSnapshot() }); + const analysis = buildRepoRewardRisk({ + login: "dev", + repo: churnRepo, + repoFullName: churnRepo.fullName, + profile, + outcomeHistory, + scoringSnapshot: scoringSnapshot(), + scoringProfile, + issues: [], + pullRequests: [], + }); + expect(analysis.riskBreakdown.reviewChurnRisk).toBe("high"); + }); + + it("reviewChurnRisk reports medium risk for a moderate closed-PR rate", () => { + const profile = buildContributorProfile("dev", { login: "dev", topLanguages: ["TypeScript"], source: "github" }, [], []); + const churnRepo = repo("owner/churn-mid"); + // One closed + two merged => closedPullRequestRate ~0.33 => risk in [20, 45) => "medium". + const outcomeHistory = buildContributorOutcomeHistory({ + login: "dev", + profile, + repositories: [churnRepo], + pullRequests: [ + pr(churnRepo.fullName, 40, "Closed one", { state: "closed" }), + pr(churnRepo.fullName, 41, "Merged one", { state: "merged", mergedAt: "2026-05-20T00:00:00.000Z" }), + pr(churnRepo.fullName, 42, "Merged two", { state: "merged", mergedAt: "2026-05-21T00:00:00.000Z" }), + ], + issues: [], + repoStats: [], + }); + const fit = buildContributorFit(profile, [churnRepo], [], [], [], []); + const scoringProfile = buildContributorScoringProfile({ login: "dev", fit, scoringSnapshot: scoringSnapshot() }); + const analysis = buildRepoRewardRisk({ + login: "dev", + repo: churnRepo, + repoFullName: churnRepo.fullName, + profile, + outcomeHistory, + scoringSnapshot: scoringSnapshot(), + scoringProfile, + issues: [], + pullRequests: [], + }); + expect(analysis.riskBreakdown.reviewChurnRisk).toBe("medium"); + }); + + it("maintainer-cut readiness scores without the low-queue bonus when the owned repo's queue is not low", () => { + // Owner === login => maintainer lane; a heavily loaded queue keeps queueHealth.level above "low", + // exercising the `level === "low" ? 20 : 0` false branch. + const ownedRepo = repo("dev/owned"); + const busyPrs = Array.from({ length: 14 }, (_, i) => pr(ownedRepo.fullName, i + 1, `Open work ${i}`, { authorLogin: `other${i}` })); + const profile = buildContributorProfile("dev", { login: "dev", topLanguages: ["TypeScript"], source: "github" }, [], []); + const fit = buildContributorFit(profile, [ownedRepo], [], [], [], []); + const scoringProfile = buildContributorScoringProfile({ login: "dev", fit, scoringSnapshot: scoringSnapshot() }); + const analysis = buildRepoRewardRisk({ + login: "dev", + repo: ownedRepo, + repoFullName: ownedRepo.fullName, + profile, + outcomeHistory: buildContributorOutcomeHistory({ login: "dev", profile, repositories: [ownedRepo], pullRequests: busyPrs, issues: [], repoStats: [] }), + scoringSnapshot: scoringSnapshot(), + scoringProfile, + issues: [], + pullRequests: busyPrs, + }); + expect(analysis.roleContext.maintainerLane).toBe(true); + expect(analysis.actions.some((a) => a.actionKind === "maintainer_cut_readiness")).toBe(true); + }); +}); diff --git a/test/unit/reward-risk-reports.test.ts b/test/unit/reward-risk-reports.test.ts index 3fed21f64f..761d010ec8 100644 --- a/test/unit/reward-risk-reports.test.ts +++ b/test/unit/reward-risk-reports.test.ts @@ -1,15 +1,19 @@ import { describe, expect, it } from "vitest"; import { + buildContributorRewardRiskStrategy, buildMaintainerNoiseReport, buildPullRequestReviewability, } from "../../src/signals/reward-risk"; import { + buildContributorFit, buildContributorOutcomeHistory, buildContributorProfile, + buildContributorScoringProfile, type ContributorOutcomeHistory, } from "../../src/signals/engine"; import type { CheckSummaryRecord, + ContributorRepoStatRecord, IssueRecord, PullRequestFileRecord, PullRequestRecord, @@ -17,6 +21,7 @@ import type { RecentMergedPullRequestRecord, RegistryRepoConfig, RepositoryRecord, + ScoringModelSnapshotRecord, } from "../../src/types"; function repo(fullName: string, overrides: Partial = {}): RepositoryRecord { @@ -108,6 +113,20 @@ function failingCheck(pullNumber: number, name = "ci"): CheckSummaryRecord { }; } +function scoringSnapshot(): ScoringModelSnapshotRecord { + 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: {}, + }; +} + describe("buildMaintainerNoiseReport (#2093)", () => { it("falls back to the empty-noise message when no source fires", () => { const r = repo("JSONbored/loopover"); @@ -462,4 +481,65 @@ describe("buildPullRequestReviewability (#2093)", () => { ); expect(result.maintainerNextSteps.length).toBeGreaterThan(0); }); +}); + +describe("buildContributorRewardRiskStrategy branch coverage (#2281)", () => { + it("breaks analysis and action ties across two identical repos", () => { + // Two byte-identical registered repos (differing only by name) produce equal analysisRank and equal + // top-action (priorityScore, actionKind) pairs, exercising the localeCompare/ACTION_RANK tie-breaks in + // both the repoAnalyses and topActions sorts, plus the fit.opportunities map callback. + const repoA = repo("twin/aaa"); + const repoB = repo("twin/bbb"); + const profile = buildContributorProfile("dev", { login: "dev", topLanguages: ["TypeScript"], source: "github" }, [], []); + const stat = (repoFullName: string): ContributorRepoStatRecord => ({ + login: "dev", + repoFullName, + pullRequests: 4, + mergedPullRequests: 2, + openPullRequests: 4, + issues: 0, + stalePullRequests: 0, + unlinkedPullRequests: 0, + dominantLabels: ["feature"], + }); + const outcomeHistory = buildContributorOutcomeHistory({ + login: "dev", + profile, + repositories: [repoA, repoB], + pullRequests: [], + issues: [], + repoStats: [stat(repoA.fullName), stat(repoB.fullName)], + }); + const fit = buildContributorFit(profile, [repoA, repoB], [], [], [], [stat(repoA.fullName), stat(repoB.fullName)]); + const scoringProfile = buildContributorScoringProfile({ login: "dev", fit, scoringSnapshot: scoringSnapshot() }); + const fitWithOpportunities = { + ...fit, + opportunities: [ + { + 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: [], + }, + ], + }; + const strategy = buildContributorRewardRiskStrategy({ + login: "dev", + fit: fitWithOpportunities, + scoringProfile, + scoringSnapshot: scoringSnapshot(), + outcomeHistory, + repositories: [repoA, repoB], + allIssues: [] as IssueRecord[], + allPullRequests: [] as PullRequestRecord[], + }); + expect(strategy.repoAnalyses).toHaveLength(2); + // Deterministic tie-break => the two identical analyses come back in lexicographic repo order. + expect(strategy.repoAnalyses.map((a) => a.repoFullName)).toEqual([repoA.fullName, repoB.fullName]); + }); }); \ No newline at end of file