182 lines
6.3 KiB
JavaScript
182 lines
6.3 KiB
JavaScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import { checkLockfilePolicy } from './dependency-policy.mjs';
|
|
import { rebuildArgs, validateRebuildEntries, verify } from './rebuild-native.mjs';
|
|
|
|
const registryOrigin = 'https://registry.npmjs.org/';
|
|
|
|
describe('dependency policy adversarial cases', () => {
|
|
it('rejects a non-link package when resolved and integrity are both missing', () => {
|
|
const violations = checkLockfilePolicy(
|
|
{
|
|
lockfileVersion: 3,
|
|
packages: {
|
|
'node_modules/evil': {
|
|
version: '1.0.0',
|
|
hasInstallScript: true,
|
|
},
|
|
},
|
|
},
|
|
['node_modules/evil'],
|
|
registryOrigin,
|
|
);
|
|
|
|
expect(violations).toContainEqual({
|
|
code: 'missing-resolved',
|
|
name: 'node_modules/evil',
|
|
});
|
|
expect(violations).toContainEqual({
|
|
code: 'missing-integrity',
|
|
name: 'node_modules/evil',
|
|
});
|
|
});
|
|
|
|
it('rejects lookalike registry hostnames', () => {
|
|
const violations = checkLockfilePolicy(
|
|
{
|
|
lockfileVersion: 3,
|
|
packages: {
|
|
'node_modules/evil': {
|
|
version: '1.0.0',
|
|
resolved: 'https://registry.npmjs.org.evil.example/evil.tgz',
|
|
integrity: 'sha512-example',
|
|
},
|
|
},
|
|
},
|
|
[],
|
|
registryOrigin,
|
|
);
|
|
|
|
expect(violations).toContainEqual({
|
|
code: 'non-registry-url',
|
|
name: 'node_modules/evil',
|
|
});
|
|
});
|
|
|
|
// このテストは名前で path / name / version / integrity の 4 つを縛ると言っている。
|
|
// ところが実際に起こしていたのは version の食い違いだけで、integrity 照合を
|
|
// 削っても 35 件すべて緑だった(PR #1074 のレビュー M8)。4 つを別々に起こす。
|
|
const soundLockfile = (over = {}) => ({
|
|
lockfileVersion: 3,
|
|
packages: {
|
|
'node_modules/native': {
|
|
version: '1.0.0',
|
|
resolved: 'https://registry.npmjs.org/native/-/native-1.0.0.tgz',
|
|
integrity: 'sha512-approved',
|
|
hasInstallScript: true,
|
|
...over,
|
|
},
|
|
},
|
|
});
|
|
const soundApproval = (over = {}) => [{
|
|
path: 'node_modules/native',
|
|
name: 'native',
|
|
version: '1.0.0',
|
|
integrity: 'sha512-approved',
|
|
verifier: 'better-sqlite3-memory-db',
|
|
...over,
|
|
}];
|
|
|
|
it('accepts an approval that matches the lockfile on every field', () => {
|
|
expect(validateRebuildEntries(soundLockfile(), soundApproval())).toHaveLength(1);
|
|
});
|
|
|
|
it('rejects an integrity mismatch even when the version agrees', () => {
|
|
expect(() => validateRebuildEntries(soundLockfile({ integrity: 'sha512-swapped' }), soundApproval()))
|
|
.toThrow(/integrity mismatch/i);
|
|
});
|
|
|
|
it('rejects a target the lockfile says has no install script', () => {
|
|
// 許可一覧に「script を持たない依存」を書けると、rebuild だけが走る経路になる
|
|
expect(() => validateRebuildEntries(soundLockfile({ hasInstallScript: false }), soundApproval()))
|
|
.toThrow(/no install script/i);
|
|
});
|
|
|
|
it('rejects a path whose last node_modules segment is not the approved name', () => {
|
|
// 経路の取り出しは lastIndexOf でなければならない。indexOf に変えると
|
|
// node_modules/a/node_modules/b が "a/node_modules/b" になり、
|
|
// 名前の食い違いを見逃す
|
|
expect(() => validateRebuildEntries(
|
|
{
|
|
lockfileVersion: 3,
|
|
packages: {
|
|
'node_modules/outer/node_modules/native': {
|
|
version: '1.0.0',
|
|
resolved: 'https://registry.npmjs.org/native/-/native-1.0.0.tgz',
|
|
integrity: 'sha512-approved',
|
|
hasInstallScript: true,
|
|
},
|
|
},
|
|
},
|
|
soundApproval({ path: 'node_modules/outer/node_modules/native', name: 'outer' }),
|
|
)).toThrow(/name mismatch/i);
|
|
});
|
|
|
|
it('accepts a nested path when the approved name matches its last segment', () => {
|
|
expect(validateRebuildEntries(
|
|
{
|
|
lockfileVersion: 3,
|
|
packages: {
|
|
'node_modules/outer/node_modules/native': {
|
|
version: '1.0.0',
|
|
resolved: 'https://registry.npmjs.org/native/-/native-1.0.0.tgz',
|
|
integrity: 'sha512-approved',
|
|
hasInstallScript: true,
|
|
},
|
|
},
|
|
},
|
|
soundApproval({ path: 'node_modules/outer/node_modules/native' }),
|
|
)).toHaveLength(1);
|
|
});
|
|
|
|
it('refuses a verifier it does not know, instead of silently skipping', () => {
|
|
// verify を即 return にしても 35 件すべて緑だった(レビュー M7)。
|
|
// 「実際にデータベースを開いて確かめる」が丸ごと消えても誰も気づかない状態。
|
|
expect(() => verify('no-such-verifier')).toThrow(/unsupported native rebuild verifier/i);
|
|
});
|
|
|
|
it('actually opens a database for the verifier this repository uses', () => {
|
|
// 中身が消えたら(即 return になったら)ここが空振りになるので、
|
|
// 検証が本当に走ったことを、走った先の失敗で見る
|
|
expect(() => verify('better-sqlite3-memory-db')).not.toThrow();
|
|
});
|
|
|
|
it('binds native rebuild permission to path, name, version, and integrity', () => {
|
|
const lockfile = {
|
|
lockfileVersion: 3,
|
|
packages: {
|
|
'node_modules/native': {
|
|
version: '2.0.0',
|
|
resolved: 'https://registry.npmjs.org/native/-/native-2.0.0.tgz',
|
|
integrity: 'sha512-new',
|
|
hasInstallScript: true,
|
|
},
|
|
},
|
|
};
|
|
const approved = [{
|
|
path: 'node_modules/native',
|
|
name: 'native',
|
|
version: '1.0.0',
|
|
integrity: 'sha512-approved',
|
|
verifier: 'better-sqlite3-memory-db',
|
|
}];
|
|
|
|
expect(() => validateRebuildEntries(lockfile, approved)).toThrow(/version mismatch/i);
|
|
});
|
|
});
|
|
|
|
describe('how npm rebuild is invoked', () => {
|
|
// 引数の並びに意味がある箇所。PR #1074 のレビューが、`--` を消しても
|
|
// 35 件すべて緑だったと報告した(M6)。
|
|
it('separates the package name from flags with --', () => {
|
|
const args = rebuildArgs('better-sqlite3');
|
|
expect(args[args.length - 2]).toBe('--');
|
|
expect(args[args.length - 1]).toBe('better-sqlite3');
|
|
});
|
|
|
|
it('builds from source instead of downloading a prebuilt binary', () => {
|
|
// 配布物を取ってこないぶん安全側。消すと取得物が 1 つ増える
|
|
expect(rebuildArgs('better-sqlite3')).toContain('--build-from-source');
|
|
});
|
|
});
|