Repository navigation
test(lgtm): pin the refusal error log, the no-label cancel debug, the token guard, the missing-head error and isForbidden's non-object arms - #582
Conversation
… token guard, the missing-head error and isForbidden's non-object arms A Stryker probe of src/labels/lgtm.ts and src/plugins/lgtmBinding.ts (npx stryker run --mutate src/labels/lgtm.ts,src/plugins/lgtmBinding.ts on the harness of #572, main @ e3119ad, Stryker 10.0.0 / Vitest 5.0.4, 8 workers, 6 m 36 s) left 24 of 202 mutants alive (88.12 %). Fourteen flip a guard or drop a log line the suite reaches but never distinguishes from its mutation. Two new test files kill all fourteen; the re-run reports 192 killed / 10 survived (95.05 %). __tests__/label/lgtmGuards.test.ts (src/labels/lgtm.ts): - 29 `getInput('github-token', { required: true })` (2 mutants): the handler fails fast with "Input required and not supplied" and posts nothing - 140 `core.error(msg)` in refuse: the refusal is logged as an error with the same text it replies with - 96 the "could not find lgtm to remove" debug line of a cancel on an issue without the label, and that no DELETE follows __tests__/plugins/lgtmBindingGuards.test.ts (src/plugins/lgtmBinding.ts): - 171 `getInput('github-token', { required: true })` (2 mutants) in lgtmOnPullRequest - 166 `pull_request?.head?.sha` -> `head.sha`: a pull_request without a head throws the "missing pull request head" TypeError, not a property read failure - 173 the "lgtm: bind_to_commit is false" debug line - 182 isForbidden `typeof error === 'object' && error !== null && 'status' in error` (6 mutants: `true` x4, `||` x2): bindLgtm and isLgtmBound wrap a null, string or number rejection in their own error instead of throwing from `'status' in <primitive>` / `null.status` Reviewed survivors, not pinned (10): lgtm.ts 33, 34 (x2), 35, 65 and lgtmBinding.ts 162, 177, 178 are optional chains on fields every issue_comment / pull_request payload carries (comment.body, comment.user, issue.user, pull_request after the head check, sender once isBotUser is true); lgtmBinding.ts 66 `targetUrl === undefined` -> false spreads `target_url: undefined`, which JSON serialisation drops, so the request body is identical; lgtmBinding.ts 156 `label?.name ?? ''` -> any other string still fails the `!== 'lgtm'` comparison and returns early. Refs #554 Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com>
|
Important Held for human review by the hive's ACMM level gate. This PR was opened by the "quality" agent while Hive policy required a human checkpoint for that agent. Agent PRs are held at ACMM L3–L5; an Hive will keep the |
There was a problem hiding this comment.
Read both new test files against the tree at 2dd1629 — this looks correct to me, and the mutant→test mapping in the body checks out.
Verified against the source:
src/labels/lgtm.ts:30—getInput('github-token', { required: true })is the first statement; the test deletesINPUT_GITHUB-TOKENand asserts no comment POST follows, which kills the{ required: true }→{}mutants.src/labels/lgtm.ts:140(refuse) —core.error(msg)is asserted exactly once with the same string the reply carries; the fixture's commenter is the issue author (Codertocaton both sides ofissueCommentEvent.json), so a plain/lgtmgenuinely reachesrefusebefore any authorization read.src/labels/lgtm.ts:96— thecould not find lgtm to removedebug plus thenotCalled()assertion on the DELETE pins both the log and the early return.src/plugins/lgtmBinding.ts:166-168— passing{ pull_request: { number: 1 } }makes the optional chain yieldundefined; the/^github context payload missing pull request head: /match distinguishes the TypeError from the property-read crash the?.-removal mutants would produce.src/plugins/lgtmBinding.ts:186(isForbidden) — the bare-value rejections (null,'boom',403) each short-circuit a different conjunct; replacing any arm withtruemakes'status' in null/null.statusthrow out of the catch instead of the wrapped message the tests assert. The octokit stub rejecting with a bare value mirrors thegetContentprecedent inauthGuards(#579).
Scope claim holds: two new test files only, disjoint from #571's edits to the existing lgtm.test.ts/lgtmBinding.test.ts and from #573 (cronJobs/lgtm.ts). The ten unpinned survivors listed in the body are correctly classed — the optional chains sit behind guards every delivered payload satisfies, and the targetUrl === undefined → false mutant produces an identical serialized request body.
No defects found. Refs #554 (non-closing) is appropriate: the tracker's other sections stay open.
— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.88
|
/kind cleanup 🐝 Hive Agent: — hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88 |
|
Consolidated into #588 together with the other eight open mutation-survivor pin PRs (commit cherry-picked unchanged, DCO preserved). Closing in favour of that PR. |
Test Improvement
A Stryker probe of the lgtm label-gating cluster —
src/labels/lgtm.tsandsrc/plugins/lgtmBinding.ts(npx stryker run --mutate src/labels/lgtm.ts,src/plugins/lgtmBinding.tson #572's harness, main @ e3119ad, Stryker 10.0.0 / Vitest 5.0.4, 8 workers, 6 m 36 s) left 24 of 202 mutants alive (88.12 %). Fourteen flip a guard or drop a log line the suite reaches but never distinguishes from its mutation. This PR adds two test files that kill all fourteen; re-running the probe with them reports 192 killed / 10 survived (95.05 %). Thelgtmlabel is the automatic-merge gate, so this is the "selected high-risk behavior" #554 asks to probe.Files / cluster claimed:
__tests__/label/lgtmGuards.test.ts(new),__tests__/plugins/lgtmBindingGuards.test.ts(new). Nosrc/,dist/or existing-test change. Disjoint from every held PR on this cluster: #571 edits the existing__tests__/label/lgtm.test.ts/__tests__/plugins/lgtmBinding.test.ts(helper call only); #557 is tide's lgtm re-check; #573 iscronJobs/lgtm.ts. Same pattern as #575 (ownersGuards), #578 (tideGuards), #579 (authGuards), #580, #581.Mutants pinned (span → test)
lgtm.ts:29:47-65{ required: true }→{}/true→falserequires the github-token input before reading anythinglgtm.ts:140:3core.error(msg)→;::error::annotationa refusal is logged as an error with the same message it replies withlgtm.ts:96:16debug string →''a cancel on an issue without the label says so in the debug log and removes nothing(also pins that no DELETE follows)lgtmBinding.ts:171:60-78{ required: true }(2 mutants)requires the github-token input before reading the configurationlgtmBinding.ts:166:24-63pull_request?.head?.sha→head.shamissing pull request headTypeErrorthrows the missing-head error when the pull request has no head, not a property read failurelgtmBinding.ts:173:16debug string →''bind_to_commit: false says so in the debug loglgtmBinding.ts:182:10-74isForbiddenconjunction — 6 mutants (true× 4,||× 2)'status' in 'boom'/null.statusthrows a TypeError out of the catch instead of the wrapped "could not bind lgtm to …" / "could not read the prow/lgtm status of …" errorbindLgtm/isLgtmBoundwraps null / a string / a number without the permission hint(an octokit stub whose statuses calls reject with a bare value — the same precedent #579 uses forgetContent)Reviewed survivors, not pinned (10)
lgtm.ts:33,34(×2),35,65;lgtmBinding.ts:162,177,178— optional chains on fields everyissue_comment/pull_requestpayload carries (comment.body,comment.user,issue.user,comment.html_url;pull_requestafter the head check has already passed;senderonceisBotUser(sender)is true). Not strictly equivalent, but unreachable from a GitHub-delivered payload; the same class test(approve): pin the github-actions[bot] login-only skip, the uncoverable-file suggestion, the notifier whitespace/marker checks, the unlabeled skip, the base-branch OWNERS probe and the required token #580 lists as "optional chains on values already guarded".lgtmBinding.ts:66:11targetUrl === undefined→false— spreadstarget_url: undefined, which JSON serialisation drops; the request body is identical.lgtmBinding.ts:156:85label?.name ?? ''→ any other string — still fails!== 'lgtm'and returns early.I have not edited
docs/contributing.md's survivor table because that file is #572's claimed ground; this list is the record until #572 lands.Validation
npx vitest run __tests__/label/lgtmGuards.test.ts __tests__/plugins/lgtmBindingGuards.test.ts→ 14 passednpx eslinton both files → cleannpm run test:coverage, 167 files / 2330 tests, 2026-10-25): 99.92 % stmts / 99.35 % branches / 100 % funcs / 99.91 % lines; both files are already at 100 % lines. E2E (npm run test:coverage:e2e, 86 files / 528 tests): 100 % lines / 99.46 % branches. This PR adds assertion strength, not line coverage.Related Issue
Refs #554 — ticks a slice of the "Strengthen assertions and the test harness" section (reviewed mutation survivors on the lgtm gate); the tracker's CI-enforcement, release-delivery and routing sections stay open and are owned elsewhere.
Filed by quality agent (hold-gated mode). Human review required.
— hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88 requested_by=@mrbobbytables