Skip to content

test(mutation): pin the milestone list separator, isBotUser's login arm, createComment's message and skipReadOnlyForkRun's payload guards - #585

Closed
hivecommons-hive[bot] wants to merge 1 commit into
mainfrom
quality/test-mutation-milestone-comments-events
Closed

hivecommons-hive[bot] wants to merge 1 commit into
mainfrom
quality/test-mutation-milestone-comments-events

Conversation

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Test Improvement

Pins nine behavioural Stryker survivors reported in #584, found by running #572's harness over the issue-comment, label and utility modules no earlier probe had covered (main @ e3119ad, 643 mutants, 79 survived; the rest are equivalent, see the issue).

Files / cluster claimed — three new test files; no src/, dist/ or existing test is edited, so this is disjoint from every held PR (#571 touches retitle.test.ts, comments.test.ts, milestone.test.ts and readOnlyFork.test.ts by one import line each; none of those files change here):

file pins
__tests__/issueCommentTest/milestoneAvailableList.test.ts /milestone v3 against three open milestones fails with Available milestones: v1, v2, backlog and issues no PATCH (milestone.ts:66, join(', '))
__tests__/utils/commentsGuards.test.ts isBotUser table — github-actions[bot] login with no type / with type: 'User' is a bot, github-actions prefix is not, {}/null/undefined are not (comments.ts:67); createComment on a 403 rejects with /^could not add comment: HttpError: Resource not accessible by integration/ (comments.ts:34); createCommentOnce skips a marker comment whose user has only the login
__tests__/pullReqTest/readOnlyForkPayloads.test.ts skipReadOnlyForkRun returns false, emits no notice and does not throw under pull_request and pull_request_review for a payload with no pull_request, no head, no repository, no full_name, or a numeric full_name; the control case with both names present still skips (events.ts:23-25)

Re-running Stryker on those lines with the pins: milestone.ts 15/15 killed, events.ts 28/28 killed, comments.ts 31/33 killed (the two survivors are the equivalent body ?? '' fallback at :58 and the debug string at :59).

Not pinned here: retitle.ts:34 (title === '' guard) — #571's failTestOnUnhandledRequests on retitle.test.ts kills it, so it is left to that PR.

Checks run locally

  • npx vitest run __tests__/issueCommentTest/milestoneAvailableList.test.ts __tests__/utils/commentsGuards.test.ts __tests__/pullReqTest/readOnlyForkPayloads.test.ts → 3 files, 17 tests pass
  • npx eslint on the three files → clean

Related Issue

Closes #584


Filed by quality agent (hold-gated mode). Human review required.

— hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88

…Comment's message and skipReadOnlyForkRun's payload guards

A Stryker probe over the issue-comment, label and utility modules left
nine behavioural survivors the unit suite reaches but never distinguishes
from their mutations. Three new files pin them:

- __tests__/issueCommentTest/milestoneAvailableList.test.ts: the
  not-found error joins the open milestones with ', ' (milestone.ts:66).
- __tests__/utils/commentsGuards.test.ts: isBotUser accepts the
  github-actions[bot] login without a Bot type (comments.ts:67),
  createComment rewraps a refused post as 'could not add comment: ...'
  (comments.ts:34), and createCommentOnce honours a login-only bot marker.
- __tests__/pullReqTest/readOnlyForkPayloads.test.ts: skipReadOnlyForkRun
  returns false rather than throwing on a payload without pull_request,
  head, repository or a string full_name (events.ts:23-25).

Closes #584

Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com>
@hivecommons-hive
hivecommons-hive Bot requested a review from jpmcb as a code owner October 26, 2026 01:36
@hivecommons-hive hivecommons-hive Bot added the hold Indicates that a PR should not merge because someone has issued a /hold command. label Oct 26, 2026
@hivecommons-hive

Copy link
Copy Markdown
Contributor Author

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 outreach PR that targets a repository this hive does not manage is held at every level because it publishes project-facing communication into someone else's project.

Hive will keep the hold label until a human removes it. Operators can make a deliberate one-off release during an ACMM level change with release_level_holds=true, but level changes never release this hold automatically.

@hivecommons-hive hivecommons-hive Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read the three new test files against the sources they pin; this looks correct to me.

  • isBotUser table matches src/utils/comments.ts:67-69 exactly: user?.type === 'Bot' || user?.login === 'github-actions[bot]' makes {login:'github-actions[bot]'} with no/User type a bot, the bare github-actions prefix and {}/null/undefined not. The 403 rewrap assertion matches the could not add comment: ${e} wrap at comments.ts:34.
  • skipReadOnlyForkRun payload cases match src/utils/events.ts:23-25: any non-string head/base full_name (including the numeric 42) falls into the typeof !== 'string' arm, returns false with no notice; the control case (differing names) returns true with one notice. Mock state across cases is safe — vitest.config.mjs:12-14 sets clearMocks/mockReset/restoreMocks.
  • The milestone assertion matches src/issueComment/milestone.ts:66-68 (titles.join(', ') into Available milestones: …), and the ObserveRequest on the PATCH pins that no milestone is set.

Diff adds three test files only — no src/, dist/, or existing test edited, so the body's disjointness claim holds. All helpers used (failOnUnhandledRequest, ObserveRequest, MockContext, setupActionsEnv, mockResponse) exist in __tests__/testUtils.ts. CI is green on head 9221ac2. No findings; fine to land once a maintainer lifts the hold.

— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.88

@hivecommons-hive

Copy link
Copy Markdown
Contributor Author

/kind cleanup


🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5q9t | SHA: e3119ad

— hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88

@github-actions github-actions Bot added kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. and removed needs-kind labels Oct 26, 2026
@mrbobbytables

Copy link
Copy Markdown
Member

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.

mrbobbytables added a commit that referenced this pull request Oct 26, 2026
, #578-#583, #585) (#588)

* test(cron): pin cronLgtm's merge count and its no-read short circuits

cronLgtm resolves to the number of pull requests it merged or enqueued,
and a pull request it skips before the shared merge path - gate failure
or locked - costs no further request. Neither was asserted: the count is
discarded by handleCronJob, and tide's own blockedReason also refuses a
locked or gated PR, so the existing merge observers could not see the
guards. A mutation run over src/cronJobs/lgtm.ts left 13 survivors; the
nine behavioural ones (jobsDone++, return false, the !gate.ok and locked
guards, { required: true } on github-token, state: 'open') are now
killed by direct cronLgtm calls that watch GET /pulls/:number. The four
left are log text.

Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com>

* test(owners): pin the per-team group cache key, parseGroup anchor, decode, defaultBranch and isNotFound guards

Running the bounded Stryker check against src/utils/owners.ts left 15
survivors at 100% line coverage. Thirteen are behavioural: with one
groupCache key every @org/team entry resolves to the first team's
members; parseGroup without its leading anchor reads alice@org/team as
a group; a bare `options:` key, a blob without content, a payload
without a repository, a string or null tree rejection and a path-less
tree entry each hit a TypeError instead of the handled path; and
repoHasOwners would answer a second repository from the first.

Each is pinned with a direct call in a new file so the harness reports
98.49% on the module, leaving only the warning text held #564 asserts
and a debug string.

Closes #574

Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com>

* test(tide): pin mergeOnce's status guard, the 403-only fork diagnosis, sorted workflow list, queue-path retryIf, entry-less dequeue and isUnknown's state arm

Each case fails when the guard it names is removed or inverted; line
coverage already reached them all (Stryker on src/plugins/tide.ts:
477/517 killed before, 488/517 after).

Refs #570
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com>

* test(auth): pin the repository-less org check, the gone-user comment skip, the non-RequestError and half-formed root OWNERS reads, and the OWNERS path in role errors

A Stryker probe of src/utils/auth.ts (--mutate src/utils/auth.ts on the
harness of #572) left 22 of 273 mutants alive. Eleven of them flip a guard
the suite reaches but never distinguishes; this file pins each one:

- checkOrgMember on a payload without a repository returns false by the
  early return, not by a TypeError caught as an unexpected error, so no
  warning annotation is emitted
- checkIssueComments skips a comment whose user is null (e.user?.login)
  instead of aborting the scan
- retrieveOwnersFile reports a string or null rejection of repos.getContent
  as the OWNERS lookup error rather than tripping on 'status' in e
- a contents response with content but no encoding, or an encoding but no
  content, is rejected as an invalid OWNERS file rather than decoded
- a root OWNERS role that is not a list is reported as 'OWNERS at OWNERS:'
  from both assertRootOwner and rootOwnersIncludes

The seven remaining survivors are core.debug strings and the
contents === '' short-circuit of rootOwnersIncludes, which is equivalent:
parseOwners('OWNERS', '') yields empty lists and expandGroups of an empty
list returns an empty set without a request.

Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com>

* test(approve): pin the github-actions[bot] login-only skip, the uncoverable-file suggestion, the notifier whitespace and marker checks, the unlabeled other-label skip, the base-branch OWNERS probe and the required token

A Stryker probe of src/plugins/approve.ts (--mutate src/plugins/approve.ts on
the harness of #572, main@e3119ad) left 59 of 515 mutants alive. Twenty-six of
them flip a guard the suite reaches but never distinguishes; this file pins
each one in __tests__/plugins/approveGuards.test.ts:

- approvalEvents skips a github-actions[bot] comment by its login alone,
  without relying on the Bot user type
- suggestApprovers still names an approver when another changed file has no
  OWNERS entry at all, instead of dereferencing the missing per-file set
- computeApproval logs the approver who covers none of the changed files and
  says nothing for a pull request without an author
- renderNotifier's no-files body is exactly the verdict, the explanation and
  the marker
- approveOnPullRequest returns on `unlabeled` of another label without a
  single api call, probes the OWNERS of the event's base branch rather than
  the default branch, and fails on a missing github-token input before
  reading anything
- upsertNotifier recognises a stale notifier by a github-actions[bot] user
  without a Bot type, skips a comment whose user is gone, treats a notifier
  that differs only by surrounding whitespace as up to date, and never
  mistakes a bot comment without the literal marker for the notifier
- approveOnReview on a payload without a review or a pull request fails on
  the missing pull request, not on the missing review

Re-running the probe with this file kills all twenty-six; the 33 that remain
are equivalent (log strings, optional chains on values already checked,
switch defaults, an excluded.add after the covered files were removed).

Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com>

* test(mergeQueue): pin errorMessage's null-rejection guards, the once-per-run warning's initial state and the [bot] suffix anchor

Adds __tests__/utils/mergeQueueGuards.test.ts. Every line and branch of
src/utils/mergeQueue.ts is already covered, but five of its mutants survive
the Stryker harness: `npx stryker run --mutate src/utils/mergeQueue.ts`
(main@e3119ad) kills 106 of 111 (score 95.50%). The survivors, each
re-applied by hand at its reported span and confirmed to pass the existing
mergeQueue.test.ts:

- mergeQueue.ts:184 `e !== null` -> `true`, the whole
  `typeof e === 'object' && e !== null && 'errors' in e` conjunction -> `true`,
  and the final `&&` -> `||`: a null or undefined rejection of octokit.graphql
  would throw a TypeError out of errorMessage, so queueState would reject
  instead of warning and falling back to the REST merge, and enqueue would
  throw instead of returning `{ ok: false }`.
- mergeQueue.ts:64 `let warnedUnavailable = false` -> `true`: every existing
  test calls resetMergeQueueWarnings() (directly or via setupActionsEnv), so
  the first real run of a job never warning went unnoticed. Pinned on a fresh
  module instance (vi.resetModules) that nothing has reset.
- mergeQueue.ts:153 `/^github-actions$|\[bot\]$/i` -> `/^github-actions$|\[bot\]/i`:
  a login merely containing `[bot]` would count as an automation whose queue
  entry the gate may undo.

With this file all five are killed (manual re-application of each mutant
fails 1-3 of the new cases; 111/111 killed).

No production code changes.

Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com>

* 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

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>

* test(trigger): pin the guards of /retest, /test, /ok-to-test and okToTestOnPullRequest that a Stryker probe left unasserted

A Stryker probe of src/issueComment/trigger.ts (346 mutants) left 38 alive.
Twenty-three of them flip a guard the suite reaches but never distinguishes
from its mutation: the react() comment-id guard, the refusal error log, the
gone-commenter and gone-author optional chains, the required github-token
input, the .yaml and full-path workflow matches, the no-match argument echo,
the ok-to-test label equality, the approved-and-labeled reaction, the
status-only action_required candidate, the null-rejection statusOf guard,
the synchronize/reopened action filter, the pull-less and head-less
payloads and the null label entry. This file adds one test per guard; the
probe re-run reports 331 killed / 15 survived (95.66 %). The 15 survivors
are equivalent or unreachable and are listed in the pull request.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com>

* test: pin the milestone list separator, isBotUser's login arm, createComment's message and skipReadOnlyForkRun's payload guards

A Stryker probe over the issue-comment, label and utility modules left
nine behavioural survivors the unit suite reaches but never distinguishes
from their mutations. Three new files pin them:

- __tests__/issueCommentTest/milestoneAvailableList.test.ts: the
  not-found error joins the open milestones with ', ' (milestone.ts:66).
- __tests__/utils/commentsGuards.test.ts: isBotUser accepts the
  github-actions[bot] login without a Bot type (comments.ts:67),
  createComment rewraps a refused post as 'could not add comment: ...'
  (comments.ts:34), and createCommentOnce honours a login-only bot marker.
- __tests__/pullReqTest/readOnlyForkPayloads.test.ts: skipReadOnlyForkRun
  returns false rather than throwing on a payload without pull_request,
  head, repository or a string full_name (events.ts:23-25).

Closes #584

Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com>

---------

Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com>
Co-authored-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mrbobbytables
mrbobbytables deleted the quality/test-mutation-milestone-comments-events branch October 26, 2026 20:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hold Indicates that a PR should not merge because someone has issued a /hold command. kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt.

Projects

None yet

1 participant