From 9666a9e4cad91c7c4af236ba8413e1c46f4dc04e Mon Sep 17 00:00:00 2001 From: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2026 15:08:38 -0400 Subject: [PATCH] test(tide): pin the gate re-check after an unknown-mergeability retry tryMergePullRequest re-reads a pull request whose mergeability is still unknown. Two cases were missing at the caller level: the lgtm label disappearing on the re-read must end the wait at that read (no 2 s and 4 s retries), and a re-read that turns clean while losing lgtm must not merge. Dropping the blockedReason(pr, tide) re-check after fetchMergeability in evaluateMerge survived every existing unit test (982) and the rebuilt bundle suite (528); the second case fails on that mutant. Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com> --- __tests__/plugins/tide.test.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/__tests__/plugins/tide.test.ts b/__tests__/plugins/tide.test.ts index 8d64039..669f114 100644 --- a/__tests__/plugins/tide.test.ts +++ b/__tests__/plugins/tide.test.ts @@ -257,6 +257,35 @@ describe('tryMergePullRequest', () => { expect(info).toHaveBeenCalledWith('skipping pr #1: not mergeable (unknown)') }) + // the gate is re-read with the state: an lgtm that disappears while the state is still + // unknown ends the wait at that read, before the remaining 2 s and 4 s retries + it('stops retrying an unknown state once the lgtm label disappears on the re-read', async () => { + const gets = servePull(pull(['lgtm'], unknown), pull([], unknown)) + const merge = observeMerge() + const info = vi.spyOn(core, 'info') + + await expect(tryMergePullRequest(octokit, context, 1, tide)).resolves.toBe('skipped') + await expect(merge.notCalled()).resolves.toBe('not called') + expect(gets).toHaveLength(2) + expect(sleep).toHaveBeenCalledExactlyOnceWith(1000) + expect(info).toHaveBeenCalledWith('skipping pr #1: missing lgtm') + expect(info).not.toHaveBeenCalledWith('mergeability of pr #1 is still unknown after 3 retries') + }) + + // the first read passed the gate, but the merge decision is made on the re-read: a state that + // becomes clean at the same moment the lgtm label is removed must not merge + it('does not merge when the re-read turns clean but has lost the lgtm label', async () => { + const gets = servePull(pull(['lgtm'], unknown), pull([])) + const merge = observeMerge() + const info = vi.spyOn(core, 'info') + + await expect(tryMergePullRequest(octokit, context, 1, tide)).resolves.toBe('skipped') + await expect(merge.notCalled()).resolves.toBe('not called') + expect(gets).toHaveLength(2) + expect(sleep).toHaveBeenCalledExactlyOnceWith(1000) + expect(info).toHaveBeenCalledWith('skipping pr #1: missing lgtm') + }) + it.each([ [['kind/bug'], 'missing lgtm'], [['lgtm', 'do-not-merge/hold'], 'blocked by do-not-merge/hold'],