mirror of
https://github.com/pnpm/pnpm.git
synced 2026-07-25 15:07:06 -04:00
* fix(audit): fallback to quick audit endpoint Fallback to /audits/quick when /audits fails with non-200, avoiding 5xx hard failures. Close #10649 * refactor(audit): reuse request options for fallback Share request options between primary and quick audit endpoints. Use POST for consistency. * fix(audit): use quick audit endpoint as primary, full as fallback --------- Co-authored-by: Zoltan Kochan <z@kochan.io>
247 lines
6.2 KiB
TypeScript
247 lines
6.2 KiB
TypeScript
import { audit } from '@pnpm/audit'
|
|
import { LOCKFILE_VERSION } from '@pnpm/constants'
|
|
import { type PnpmError } from '@pnpm/error'
|
|
import { fixtures } from '@pnpm/test-fixtures'
|
|
import { type DepPath, type ProjectId } from '@pnpm/types'
|
|
import nock from 'nock'
|
|
import { lockfileToAuditTree } from '../lib/lockfileToAuditTree.js'
|
|
|
|
const f = fixtures(import.meta.dirname)
|
|
|
|
describe('audit', () => {
|
|
test('lockfileToAuditTree()', async () => {
|
|
expect(await lockfileToAuditTree({
|
|
importers: {
|
|
['.' as ProjectId]: {
|
|
dependencies: {
|
|
foo: '1.0.0',
|
|
},
|
|
specifiers: {
|
|
foo: '^1.0.0',
|
|
},
|
|
},
|
|
},
|
|
lockfileVersion: LOCKFILE_VERSION,
|
|
packages: {
|
|
['bar@1.0.0' as DepPath]: {
|
|
resolution: {
|
|
integrity: 'bar-integrity',
|
|
},
|
|
},
|
|
['foo@1.0.0' as DepPath]: {
|
|
dependencies: {
|
|
bar: '1.0.0',
|
|
},
|
|
resolution: {
|
|
integrity: 'foo-integrity',
|
|
},
|
|
},
|
|
},
|
|
}, { lockfileDir: f.find('one-project') })).toEqual({
|
|
name: undefined,
|
|
version: undefined,
|
|
|
|
dependencies: {
|
|
'.': {
|
|
dependencies: {
|
|
foo: {
|
|
dependencies: {
|
|
bar: {
|
|
dev: false,
|
|
integrity: 'bar-integrity',
|
|
version: '1.0.0',
|
|
},
|
|
},
|
|
dev: false,
|
|
integrity: 'foo-integrity',
|
|
requires: {
|
|
bar: '1.0.0',
|
|
},
|
|
version: '1.0.0',
|
|
},
|
|
},
|
|
dev: false,
|
|
requires: {
|
|
foo: '1.0.0',
|
|
},
|
|
version: '1.0.0',
|
|
},
|
|
},
|
|
dev: false,
|
|
install: [],
|
|
integrity: undefined,
|
|
metadata: {},
|
|
remove: [],
|
|
requires: { '.': '1.0.0' },
|
|
})
|
|
})
|
|
|
|
test('lockfileToAuditTree() without specified version should use default version 0.0.0', async () => {
|
|
expect(await lockfileToAuditTree({
|
|
importers: {
|
|
['.' as ProjectId]: {
|
|
dependencies: {
|
|
foo: '1.0.0',
|
|
},
|
|
specifiers: {
|
|
foo: '^1.0.0',
|
|
},
|
|
},
|
|
},
|
|
lockfileVersion: LOCKFILE_VERSION,
|
|
packages: {
|
|
['bar@1.0.0' as DepPath]: {
|
|
resolution: {
|
|
integrity: 'bar-integrity',
|
|
},
|
|
},
|
|
['foo@1.0.0' as DepPath]: {
|
|
dependencies: {
|
|
bar: '1.0.0',
|
|
},
|
|
resolution: {
|
|
integrity: 'foo-integrity',
|
|
},
|
|
},
|
|
},
|
|
}, { lockfileDir: f.find('project-without-version') })).toEqual({
|
|
name: undefined,
|
|
version: undefined,
|
|
|
|
dependencies: {
|
|
'.': {
|
|
dependencies: {
|
|
foo: {
|
|
dependencies: {
|
|
bar: {
|
|
dev: false,
|
|
integrity: 'bar-integrity',
|
|
version: '1.0.0',
|
|
},
|
|
},
|
|
dev: false,
|
|
integrity: 'foo-integrity',
|
|
requires: {
|
|
bar: '1.0.0',
|
|
},
|
|
version: '1.0.0',
|
|
},
|
|
},
|
|
dev: false,
|
|
requires: {
|
|
foo: '1.0.0',
|
|
},
|
|
version: '0.0.0',
|
|
},
|
|
},
|
|
dev: false,
|
|
install: [],
|
|
integrity: undefined,
|
|
metadata: {},
|
|
remove: [],
|
|
requires: { '.': '0.0.0' },
|
|
})
|
|
})
|
|
|
|
test('an error is thrown if the audit endpoint responds with a non-OK code', async () => {
|
|
const registry = 'http://registry.registry/'
|
|
const getAuthHeader = () => undefined
|
|
nock(registry, {
|
|
badheaders: ['authorization'],
|
|
})
|
|
.post('/-/npm/v1/security/audits/quick')
|
|
.reply(500, { message: 'Something bad happened' })
|
|
nock(registry, {
|
|
badheaders: ['authorization'],
|
|
})
|
|
.post('/-/npm/v1/security/audits')
|
|
.reply(500, { message: 'Fallback failed too' })
|
|
|
|
let err!: PnpmError
|
|
try {
|
|
await audit({
|
|
importers: {},
|
|
lockfileVersion: LOCKFILE_VERSION,
|
|
},
|
|
getAuthHeader,
|
|
{
|
|
lockfileDir: f.find('one-project'),
|
|
registry,
|
|
retry: {
|
|
retries: 0,
|
|
},
|
|
virtualStoreDirMaxLength: 120,
|
|
})
|
|
} catch (_err: any) { // eslint-disable-line
|
|
err = _err
|
|
}
|
|
|
|
expect(err).toBeDefined()
|
|
expect(err.code).toBe('ERR_PNPM_AUDIT_BAD_RESPONSE')
|
|
expect(err.message).toBe('The audit endpoint (at http://registry.registry/-/npm/v1/security/audits/quick) responded with 500: {"message":"Something bad happened"}. Fallback endpoint (at http://registry.registry/-/npm/v1/security/audits) responded with 500: {"message":"Fallback failed too"}')
|
|
})
|
|
|
|
test('falls back to /audits if /audits/quick fails', async () => {
|
|
const registry = 'http://registry.registry/'
|
|
const getAuthHeader = () => undefined
|
|
nock(registry, {
|
|
badheaders: ['authorization'],
|
|
})
|
|
.post('/-/npm/v1/security/audits/quick')
|
|
.reply(500, { message: 'Something bad happened' })
|
|
nock(registry, {
|
|
badheaders: ['authorization'],
|
|
})
|
|
.post('/-/npm/v1/security/audits')
|
|
.reply(200, {
|
|
actions: [],
|
|
advisories: {},
|
|
metadata: {
|
|
dependencies: 0,
|
|
devDependencies: 0,
|
|
optionalDependencies: 0,
|
|
totalDependencies: 0,
|
|
vulnerabilities: {
|
|
critical: 0,
|
|
high: 0,
|
|
info: 0,
|
|
low: 0,
|
|
moderate: 0,
|
|
},
|
|
},
|
|
muted: [],
|
|
})
|
|
|
|
expect(await audit({
|
|
importers: {},
|
|
lockfileVersion: LOCKFILE_VERSION,
|
|
},
|
|
getAuthHeader,
|
|
{
|
|
lockfileDir: f.find('one-project'),
|
|
registry,
|
|
retry: {
|
|
retries: 0,
|
|
},
|
|
virtualStoreDirMaxLength: 120,
|
|
})).toEqual({
|
|
actions: [],
|
|
advisories: {},
|
|
metadata: {
|
|
dependencies: 0,
|
|
devDependencies: 0,
|
|
optionalDependencies: 0,
|
|
totalDependencies: 0,
|
|
vulnerabilities: {
|
|
critical: 0,
|
|
high: 0,
|
|
info: 0,
|
|
low: 0,
|
|
moderate: 0,
|
|
},
|
|
},
|
|
muted: [],
|
|
})
|
|
})
|
|
})
|