From 04a2c9c6101cea77997dd4eceae36fdefa78481e Mon Sep 17 00:00:00 2001 From: Tim Haines Date: Wed, 20 May 2026 08:02:49 +0900 Subject: [PATCH] test(outdated): add regression test for minimumReleaseAge (#11699) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a regression test for #11698. The underlying fix already shipped as part of #11705 (which removed `strictPublishedByCheck` entirely and routed maturity decisions through `policyViolation`), so this PR now lands only the dedicated test that locks in the behavior. ## What the test covers `deps/inspection/commands/test/outdated/minimumReleaseAge.test.ts`: - **Baseline** — without an age policy, `pnpm outdated` reports `is-negative@2.1.0` as an available upgrade (sanity check that the fixture actually has outdated deps). - **Regression** — with `minimumReleaseAge` set to a cutoff so far in the past that every published version is immature, `pnpm outdated` reports nothing: `exitCode === 0` and `2.1.0` does not appear. Before #11705 this test went red because the non-strict resolver fallback re-picked the immature `latest` ignoring `publishedBy`. The `allImmatureMinimumReleaseAge = Date.now() / (60 * 1000)` trick (cutoff = epoch in minutes) is date-independent and matches the technique already used in the install-side `minimumReleaseAge` suite. ## Why a test-only PR The original PR proposed flipping `strictPublishedByCheck` in `createManifestGetter`, but #11705 deleted that option entirely and replaced it with an always-defer model (`policyViolation` flows through `ResolveResult` → `getManifest` returns `null` on `MINIMUM_RELEASE_AGE_VIOLATION`). The test was the durable contribution; preserving it as a regression gate is worth keeping. --- .../test/outdated/minimumReleaseAge.test.ts | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 deps/inspection/commands/test/outdated/minimumReleaseAge.test.ts diff --git a/deps/inspection/commands/test/outdated/minimumReleaseAge.test.ts b/deps/inspection/commands/test/outdated/minimumReleaseAge.test.ts new file mode 100644 index 0000000000..bae79b4c36 --- /dev/null +++ b/deps/inspection/commands/test/outdated/minimumReleaseAge.test.ts @@ -0,0 +1,74 @@ +/// +import fs from 'node:fs' +import path from 'node:path' +import { stripVTControlCharacters as stripAnsi } from 'node:util' + +import { expect, test } from '@jest/globals' +import { outdated } from '@pnpm/deps.inspection.commands' +import { tempDir } from '@pnpm/prepare' +import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock' +import { fixtures } from '@pnpm/test-fixtures' + +const f = fixtures(import.meta.dirname) +const hasOutdatedDepsFixture = f.find('has-outdated-deps') + +const REGISTRY_URL = `http://localhost:${REGISTRY_MOCK_PORT}` + +const OUTDATED_OPTIONS = { + cacheDir: 'cache', + fetchRetries: 1, + fetchRetryFactor: 1, + fetchRetryMaxtimeout: 60, + fetchRetryMintimeout: 10, + global: false, + networkConcurrency: 16, + offline: false, + configByUri: {}, + registries: { default: REGISTRY_URL }, + strictSsl: false, + tag: 'latest', + userAgent: '', + userConfig: {}, +} + +function loadHasOutdatedDeps (): void { + tempDir() + fs.mkdirSync(path.resolve('node_modules/.pnpm'), { recursive: true }) + fs.copyFileSync(path.join(hasOutdatedDepsFixture, 'node_modules/.pnpm/lock.yaml'), path.resolve('node_modules/.pnpm/lock.yaml')) + fs.copyFileSync(path.join(hasOutdatedDepsFixture, 'package.json'), path.resolve('package.json')) +} + +// A cutoff so far in the past that EVERY published version is "too new" to be +// mature — the same technique as the install-side minimumReleaseAge suite +// (allImmatureMinimumReleaseAge). Date-independent, so it does not depend on +// any registry-mock package's historical publish timestamps. +const allImmatureMinimumReleaseAge = Date.now() / (60 * 1000) + +test('pnpm outdated baseline (no minimumReleaseAge): newer versions are offered', async () => { + loadHasOutdatedDeps() + + const { output, exitCode } = await outdated.handler({ + ...OUTDATED_OPTIONS, + dir: process.cwd(), + }) + + // Sanity: without an age policy, outdated offers the newest registry versions. + expect(exitCode).toBe(1) + expect(stripAnsi(output)).toContain('is-negative') + expect(stripAnsi(output)).toContain('2.1.0') +}) + +test('pnpm outdated honors minimumReleaseAge: immature newer versions are not offered', async () => { + loadHasOutdatedDeps() + + const { output, exitCode } = await outdated.handler({ + ...OUTDATED_OPTIONS, + dir: process.cwd(), + minimumReleaseAge: allImmatureMinimumReleaseAge, + }) + + // With every version immature, no upgrade target is mature, so outdated has + // nothing to report — exitCode 0 and 2.1.0 must not appear as available. + expect(exitCode).toBe(0) + expect(stripAnsi(output)).not.toContain('2.1.0') +})