also looking at this
fix: let the satisfied-PR closer consult the manifest, and reject phantom paths from PR bodies
#1447
2 files
+134
-11
| @@ -364,6 +364,46 @@ describe('buddy orchestration', () => { | ||
| 364 | 364 | expect(provider.deleteBranch).toHaveBeenCalledWith('buddy/update-lodash') |
| 365 | 365 | }) |
| 366 | 366 | |
| 367 | it('success case - closes a fully satisfied PR by consulting the manifest when its packages left the scan', async () => { | |
| 368 | // The scan lists only what is still outdated, so a fully satisfied PR | |
| 369 | // has no packages in it at all — the manifest is the positive evidence. | |
| 370 | const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'buddy-close-')) | |
| 371 | fs.writeFileSync(path.join(projectDir, 'package.json'), JSON.stringify({ | |
| 372 | devDependencies: { typescript: '^7.0.2' }, | |
| 373 | })) | |
| 374 | restorers.push(() => fs.rmSync(projectDir, { recursive: true, force: true })) | |
| 375 | ||
| 376 | stubScan([makeUpdate({ name: 'react', currentVersion: '18.0.0', newVersion: '18.2.0' })]) | |
| 377 | const provider = makeCloseProvider([makePR({ | |
| 378 | number: 125, | |
| 379 | head: 'buddy/update-typescript', | |
| 380 | body: `updates${serializeManifest([makeUpdate({ name: 'typescript', currentVersion: '6.0.3', newVersion: '7.0.2' })])}`, | |
| 381 | })]) | |
| 382 | const buddy = new Buddy(baseConfig(), projectDir) | |
| 383 | ||
| 384 | await buddy.checkAndCloseSatisfiedPRs(provider, false) | |
| 385 | ||
| 386 | expect(provider.closePullRequest).toHaveBeenCalledWith(125) | |
| 387 | const [, comment] = provider.createComment.mock.calls[0] | |
| 388 | expect(comment).toContain('typescript') | |
| 389 | }) | |
| 390 | ||
| 391 | it('failure case - keeps the PR when the manifest declares a version still behind the target', async () => { | |
| 392 | const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'buddy-close-')) | |
| 393 | fs.writeFileSync(path.join(projectDir, 'package.json'), JSON.stringify({ | |
| 394 | dependencies: { lodash: '^4.17.20' }, | |
| 395 | })) | |
| 396 | restorers.push(() => fs.rmSync(projectDir, { recursive: true, force: true })) | |
| 397 | ||
| 398 | stubScan([makeUpdate({ name: 'react', currentVersion: '18.0.0', newVersion: '18.2.0' })]) | |
| 399 | const provider = makeCloseProvider([lodashPR()]) | |
| 400 | const buddy = new Buddy(baseConfig(), projectDir) | |
| 401 | ||
| 402 | await buddy.checkAndCloseSatisfiedPRs(provider, false) | |
| 403 | ||
| 404 | expect(provider.closePullRequest).not.toHaveBeenCalled() | |
| 405 | }) | |
| 406 | ||
| 367 | 407 | it('failure case - never closes on an empty scan (mass-closure guard)', async () => { |
| 368 | 408 | // A scan returning nothing may mean bun outdated failed or the registry |
| 369 | 409 | // rate-limited — closing every PR on that evidence would wipe them out. |
| @@ -430,6 +470,31 @@ describe('buddy orchestration', () => { | ||
| 430 | 470 | }) |
| 431 | 471 | }) |
| 432 | 472 | |
| 473 | describe('extractFilePathsFromPRBody', () => { | |
| 474 | it('failure case - renovate-era badge tables yield no phantom paths', () => { | |
| 475 | const buddy = new Buddy(baseConfig()) as any | |
| 476 | const body = [ | |
| 477 | '| Package | Change | Age | Adoption | Passing | Confidence |', | |
| 478 | '|---|---|---|---|---|---|', | |
| 479 | '| [typescript](https://github.com/microsoft/TypeScript/tree/master) | [`6.0.3` -> `7.0.2`](https://renovatebot.com/diffs/npm/typescript/6.0.3/7.0.2) | [](https://docs.renovatebot.com/merge-confidence/) | [](https://docs.renovatebot.com/merge-confidence/) | ok | ok |', | |
| 480 | ].join('\n') | |
| 481 | ||
| 482 | expect(buddy.extractFilePathsFromPRBody(body)).toEqual([]) | |
| 483 | }) | |
| 484 | ||
| 485 | it('success case - real file cells and bold paths still extract', () => { | |
| 486 | const buddy = new Buddy(baseConfig()) as any | |
| 487 | const body = [ | |
| 488 | '| [lodash](https://npmjs.com/lodash) | ^4.17.20 → ^4.17.21 | **package.json** | ✅ |', | |
| 489 | 'Also touches **.github/workflows/ci.yml** along the way.', | |
| 490 | ].join('\n') | |
| 491 | ||
| 492 | const paths = buddy.extractFilePathsFromPRBody(body) | |
| 493 | expect(paths).toContain('package.json') | |
| 494 | expect(paths).toContain('.github/workflows/ci.yml') | |
| 495 | }) | |
| 496 | }) | |
| 497 | ||
| 433 | 498 | describe('compareVersionsSafe', () => { |
| 434 | 499 | it('distinguishes at-or-beyond, behind, and failed', () => { |
| 435 | 500 | const buddy = new Buddy(baseConfig()) as any |