2026-03-10 00:00:00 -07:00
// src/test/cli/packaging.test.ts — packaging and entrypoint hardening tests
// Ensures exactly one canonical invocation path and no broken alternatives.
import { describe , it } from 'node:test' ;
import assert from 'node:assert' ;
import { spawnSync } from 'node:child_process' ;
import { existsSync , mkdirSync , writeFileSync , rmSync , readFileSync } from 'node:fs' ;
import { join } from 'node:path' ;
import { tmpdir } from 'node:os' ;
const ROOT = new URL ( '../../../' , import . meta . url ) . pathname ;
const DIST_CLI = join ( ROOT , 'dist/cli/index.js' ) ;
const PACKAGE_JSON = join ( ROOT , 'package.json' ) ;
function run ( args : string [ ] , cwd? : string ) {
const result = spawnSync ( 'node' , [ DIST_CLI , . . . args ] , {
encoding : 'utf8' ,
cwd : cwd || ROOT ,
timeout : 30000 ,
} ) ;
return {
stdout : result.stdout || '' ,
stderr : result.stderr || '' ,
status : result.status ,
signal : result.signal ,
} ;
}
describe ( 'packaging' , ( ) = > {
it ( 'dist/cli/index.js exists after build' , ( ) = > {
assert ( existsSync ( DIST_CLI ) , ` Missing ${ DIST_CLI } — run npm run build first ` ) ;
} ) ;
it ( '--help exits 0 and prints expected help text' , ( ) = > {
const { stdout , status } = run ( [ '--help' ] ) ;
assert . strictEqual ( status , 0 , ` Expected exit 0, got ${ status } . stderr: ${ run ( [ '--help' ] ) . stderr } ` ) ;
assert ( stdout . includes ( 'apophis' ) , 'Help should mention apophis' ) ;
assert ( stdout . includes ( 'Commands:' ) , 'Help should list commands' ) ;
assert ( stdout . includes ( 'init' ) , 'Help should mention init' ) ;
assert ( stdout . includes ( 'verify' ) , 'Help should mention verify' ) ;
} ) ;
it ( '--version exits 0 and prints version' , ( ) = > {
const { stdout , status } = run ( [ '--version' ] ) ;
assert . strictEqual ( status , 0 , ` Expected exit 0, got ${ status } ` ) ;
2026-05-21 20:39:36 -07:00
assert . match ( stdout , /2\.7\.0/ , ` Version should include 2.7.0, got: ${ stdout } ` ) ;
2026-03-10 00:00:00 -07:00
} ) ;
it ( 'init --help exits 0 and prints init help' , ( ) = > {
const { stdout , status } = run ( [ 'init' , '--help' ] ) ;
assert . strictEqual ( status , 0 ) ;
assert ( stdout . includes ( 'apophis init' ) , 'Should show init help header' ) ;
assert ( stdout . includes ( '--preset' ) , 'Should mention --preset' ) ;
} ) ;
it ( 'verify --help exits 0 and prints verify help' , ( ) = > {
const { stdout , status } = run ( [ 'verify' , '--help' ] ) ;
assert . strictEqual ( status , 0 ) ;
assert ( stdout . includes ( 'apophis verify' ) , 'Should show verify help header' ) ;
assert ( stdout . includes ( '--routes' ) , 'Should mention --routes' ) ;
} ) ;
it ( 'frobnicate exits 2 with "Unknown command"' , ( ) = > {
const { stdout , stderr , status } = run ( [ 'frobnicate' ] ) ;
assert . strictEqual ( status , 2 , ` Expected exit 2, got ${ status } ` ) ;
const combined = stdout + stderr ;
assert ( combined . includes ( 'Unknown command' ) , ` Should report unknown command. Got: ${ combined } ` ) ;
} ) ;
it ( 'verify --unknown-flag exits 2 with "Unknown flag"' , ( ) = > {
const { stdout , stderr , status } = run ( [ 'verify' , '--unknown-flag' ] ) ;
assert . strictEqual ( status , 2 , ` Expected exit 2, got ${ status } ` ) ;
const combined = stdout + stderr ;
assert ( combined . includes ( 'Unknown flag' ) , ` Should report unknown flag. Got: ${ combined } ` ) ;
} ) ;
it ( 'doctor --mode verify does not reject --mode as unknown' , ( ) = > {
const { stdout , stderr , status } = run ( [ 'doctor' , '--mode' , 'verify' , '--cwd' , 'src/cli/__fixtures__/tiny-fastify' ] ) ;
const combined = stdout + stderr ;
assert . notStrictEqual ( status , 3 , ` Should not crash. Output: ${ combined } ` ) ;
assert ( ! combined . includes ( 'Unknown flag: --mode' ) , ` Should accept --mode flag. Output: ${ combined } ` ) ;
} ) ;
// For each of the 7 commands, verify they do NOT print "Not yet implemented"
2026-05-22 13:47:18 -07:00
const commands = [ 'init' , 'verify' , 'qualify' , 'replay' , 'doctor' , 'migrate' ] ;
2026-03-10 00:00:00 -07:00
for ( const cmd of commands ) {
it ( ` ${ cmd } does not print "Not yet implemented" ` , ( ) = > {
// Some commands may fail for config reasons; we just assert they don't say "Not yet implemented"
const { stdout , stderr } = run ( [ cmd ] ) ;
const combined = stdout + stderr ;
assert (
! combined . includes ( 'Not yet implemented' ) ,
` Command ${ cmd } appears to be a placeholder. Output: ${ combined } `
) ;
} ) ;
}
it ( 'npx apophis --help works via temp package.json bin reference' , ( ) = > {
const tmpDir = join ( tmpdir ( ) , ` apophis-packaging-test- ${ Date . now ( ) } ` ) ;
mkdirSync ( tmpDir , { recursive : true } ) ;
const pkg = {
name : 'test-consumer' ,
version : '1.0.0' ,
dependencies : {
2026-05-21 20:39:36 -07:00
'@apophis/fastify' : ` file: ${ ROOT } ` ,
2026-03-10 00:00:00 -07:00
} ,
} ;
writeFileSync ( join ( tmpDir , 'package.json' ) , JSON . stringify ( pkg , null , 2 ) ) ;
// We don't actually npm install; instead we verify the bin path resolves correctly
// by checking the package.json bin field points to dist/cli/index.js
const rootPkg = JSON . parse ( readFileSync ( PACKAGE_JSON , 'utf8' ) ) ;
assert . strictEqual ( rootPkg . bin . apophis , 'dist/cli/index.js' , 'package.json bin must point to dist/cli/index.js' ) ;
assert . strictEqual ( rootPkg . main , 'dist/index.js' , 'package.json main must point to dist/index.js' ) ;
// Verify the file exists at the resolved path
const resolvedBin = join ( ROOT , rootPkg . bin . apophis ) ;
assert ( existsSync ( resolvedBin ) , ` Resolved bin path does not exist: ${ resolvedBin } ` ) ;
// Clean up temp dir
rmSync ( tmpDir , { recursive : true , force : true } ) ;
} ) ;
it ( 'npm pack produces a tarball with the bin entry' , ( ) = > {
const result = spawnSync ( 'npm' , [ 'pack' , '--dry-run' , '--json' ] , {
cwd : ROOT ,
encoding : 'utf8' ,
timeout : 30000 ,
} ) ;
assert . strictEqual ( result . status , 0 , ` npm pack --dry-run failed: ${ result . stderr } ` ) ;
const packOutput = JSON . parse ( result . stdout ) ;
const files = packOutput [ 0 ] ? . files ? . map ( ( f : { path : string } ) = > f . path ) || [ ] ;
assert ( files . includes ( 'dist/cli/index.js' ) , 'Tarball must include dist/cli/index.js' ) ;
} ) ;
2026-05-21 20:39:36 -07:00
it ( 'npm pack does not contain dist/test files' , ( ) = > {
const result = spawnSync ( 'npm' , [ 'pack' , '--dry-run' , '--json' ] , {
cwd : ROOT , encoding : 'utf8' , timeout : 30000 ,
} ) ;
assert . strictEqual ( result . status , 0 , ` npm pack failed: ${ result . stderr } ` ) ;
const packOutput = JSON . parse ( result . stdout ) ;
const files = packOutput [ 0 ] ? . files ? . map ( ( f : { path : string } ) = > f . path ) || [ ] ;
const testFiles = files . filter ( ( f : string ) = > f . includes ( 'dist/test/' ) ) ;
assert . strictEqual ( testFiles . length , 0 , ` Package must not contain dist/test/ files. Found: ${ testFiles . join ( ', ' ) } ` ) ;
} ) ;
it ( 'real consumer can import the package after install' , ( ) = > {
const tmpDir = join ( tmpdir ( ) , ` apophis-consumer- ${ Date . now ( ) } ` ) ;
mkdirSync ( tmpDir , { recursive : true } ) ;
try {
const pkg = {
name : 'test-consumer-import' ,
version : '1.0.0' ,
type : 'module' ,
dependencies : {
'@apophis/fastify' : ` file: ${ ROOT } ` ,
} ,
} ;
writeFileSync ( join ( tmpDir , 'package.json' ) , JSON . stringify ( pkg ) ) ;
const installResult = spawnSync ( 'npm' , [ 'install' , '--silent' , '--install-strategy=nested' ] , {
cwd : tmpDir , encoding : 'utf8' , timeout : 120000 ,
} ) ;
assert . strictEqual ( installResult . status , 0 , ` npm install failed: ${ installResult . stderr } ` ) ;
const importRootResult = spawnSync ( 'node' , [ '-e' , "import('@apophis/fastify').then(m => console.log('OK:', Object.keys(m))).catch(e => { console.error('FAIL:', e.message); process.exit(1) })" ] , {
cwd : tmpDir , encoding : 'utf8' , timeout : 30000 ,
} ) ;
assert . strictEqual ( importRootResult . status , 0 , ` Import root failed: ${ importRootResult . stderr } ` ) ;
assert . ok ( importRootResult . stdout . includes ( 'OK:' ) , ` Import should print OK, got: ${ importRootResult . stdout } ` ) ;
const importExtResult = spawnSync ( 'node' , [ '-e' , "import('@apophis/fastify/extensions').then(m => console.log('EXT OK:', Object.keys(m))).catch(e => { console.error('EXT FAIL:', e.message); process.exit(1) })" ] , {
cwd : tmpDir , encoding : 'utf8' , timeout : 30000 ,
} ) ;
assert . strictEqual ( importExtResult . status , 0 , ` Import extensions failed: ${ importExtResult . stderr } ` ) ;
assert . ok ( importExtResult . stdout . includes ( 'EXT OK:' ) , ` Extensions import should print OK, got: ${ importExtResult . stdout } ` ) ;
const binResult = spawnSync ( 'node' , [ join ( tmpDir , 'node_modules/.bin/apophis' ) , '--version' ] , {
cwd : tmpDir , encoding : 'utf8' , timeout : 30000 ,
} ) ;
assert . strictEqual ( binResult . status , 0 , ` CLI bin failed: ${ binResult . stderr } ` ) ;
assert . ok ( binResult . stdout . includes ( '2.' ) , ` CLI should print version starting with 2., got: ${ binResult . stdout } ` ) ;
} finally {
rmSync ( tmpDir , { recursive : true , force : true } ) ;
}
} ) ;
it ( 'TypeScript consumer can import and typecheck' , ( ) = > {
const tmpDir = join ( tmpdir ( ) , ` apophis-ts-consumer- ${ Date . now ( ) } ` ) ;
mkdirSync ( tmpDir , { recursive : true } ) ;
try {
writeFileSync ( join ( tmpDir , 'package.json' ) , JSON . stringify ( {
name : 'ts-test' ,
version : '1.0.0' ,
type : 'module' ,
dependencies : {
'@apophis/fastify' : ` file: ${ ROOT } ` ,
'fastify' : '^5.0.0' ,
'@fastify/swagger' : '^9.0.0' ,
} ,
} ) ) ;
const installResult = spawnSync ( 'npm' , [ 'install' , '--silent' , '--install-strategy=nested' ] , {
cwd : tmpDir , encoding : 'utf8' , timeout : 120000 ,
} ) ;
assert . strictEqual ( installResult . status , 0 , ` npm install failed: ${ installResult . stderr } ` ) ;
writeFileSync ( join ( tmpDir , 'consumer.ts' ) , `
import Fastify from 'fastify'
import apophis, { createAuthExtension } from '@apophis/fastify'
import { jwtExtension } from '@apophis/fastify/extensions'
import { sseExtension } from '@apophis/fastify/extensions/sse'
import { websocketExtension } from '@apophis/fastify/extensions/websocket'
import { createSerializerExtension, createSerializerRegistry } from '@apophis/fastify/extensions/serializers'
import { createHeaderExtension } from '@apophis/fastify/extension/factories'
const app = Fastify()
app.register(apophis, {
extensions: [jwtExtension, sseExtension, websocketExtension],
})
` ) ;
writeFileSync ( join ( tmpDir , 'tsconfig.json' ) , JSON . stringify ( {
compilerOptions : {
target : 'es2020' ,
module : 'nodenext' ,
moduleResolution : 'nodenext' ,
strict : true ,
skipLibCheck : true ,
} ,
include : [ 'consumer.ts' ] ,
} ) ) ;
const tsc = join ( ROOT , 'node_modules/.bin/tsc' ) ;
const tscResult = spawnSync ( tsc , [ '--noEmit' , '--project' , tmpDir ] , {
cwd : tmpDir , encoding : 'utf8' , timeout : 60000 ,
} ) ;
if ( tscResult . status !== 0 ) {
console . error ( 'TSC errors:' , tscResult . stdout + tscResult . stderr ) ;
}
assert . strictEqual ( tscResult . status , 0 , ` TypeScript typecheck must pass cleanly. Got: \ n ${ tscResult . stdout } ${ tscResult . stderr } ` ) ;
} finally {
rmSync ( tmpDir , { recursive : true , force : true } ) ;
}
} ) ;
2026-03-10 00:00:00 -07:00
it ( 'npx apophis --help works in a temp project after npm install' , ( ) = > {
const tmpDir = join ( tmpdir ( ) , ` apophis-npx-test- ${ Date . now ( ) } ` ) ;
mkdirSync ( tmpDir , { recursive : true } ) ;
const pkg = {
name : 'npx-test' ,
version : '1.0.0' ,
dependencies : {
2026-05-21 20:39:36 -07:00
'@apophis/fastify' : ` file: ${ ROOT } ` ,
2026-03-10 00:00:00 -07:00
} ,
} ;
writeFileSync ( join ( tmpDir , 'package.json' ) , JSON . stringify ( pkg , null , 2 ) ) ;
const installResult = spawnSync ( 'npm' , [ 'install' , '--silent' ] , {
cwd : tmpDir ,
encoding : 'utf8' ,
timeout : 120000 ,
} ) ;
assert . strictEqual ( installResult . status , 0 , ` npm install failed: ${ installResult . stderr } ` ) ;
const helpResult = spawnSync ( 'npx' , [ 'apophis' , '--help' ] , {
cwd : tmpDir ,
encoding : 'utf8' ,
timeout : 30000 ,
} ) ;
assert . strictEqual ( helpResult . status , 0 , ` npx apophis --help failed: ${ helpResult . stderr } ` ) ;
assert ( helpResult . stdout . includes ( 'apophis' ) , 'Help should mention apophis' ) ;
rmSync ( tmpDir , { recursive : true , force : true } ) ;
} ) ;
it ( 'npx apophis doctor works in a temp project after npm install' , ( ) = > {
const tmpDir = join ( tmpdir ( ) , ` apophis-npx-test- ${ Date . now ( ) } ` ) ;
mkdirSync ( tmpDir , { recursive : true } ) ;
const pkg = {
name : 'npx-test' ,
version : '1.0.0' ,
dependencies : {
2026-05-21 20:39:36 -07:00
'@apophis/fastify' : ` file: ${ ROOT } ` ,
2026-03-10 00:00:00 -07:00
} ,
} ;
writeFileSync ( join ( tmpDir , 'package.json' ) , JSON . stringify ( pkg , null , 2 ) ) ;
const installResult = spawnSync ( 'npm' , [ 'install' , '--silent' ] , {
cwd : tmpDir ,
encoding : 'utf8' ,
timeout : 120000 ,
} ) ;
assert . strictEqual ( installResult . status , 0 , ` npm install failed: ${ installResult . stderr } ` ) ;
const doctorResult = spawnSync ( 'npx' , [ 'apophis' , 'doctor' ] , {
cwd : tmpDir ,
encoding : 'utf8' ,
timeout : 30000 ,
} ) ;
// doctor exits non-zero when peer deps are missing in a bare temp project,
// but it should still run and print the header
assert ( doctorResult . stdout . includes ( 'APOPHIS Doctor' ) , ` Doctor should run and print header. stdout: ${ doctorResult . stdout } stderr: ${ doctorResult . stderr } ` ) ;
rmSync ( tmpDir , { recursive : true , force : true } ) ;
} ) ;
it ( 'declares supported Node policy and default confidence test path' , ( ) = > {
const rootPkg = JSON . parse ( readFileSync ( PACKAGE_JSON , 'utf8' ) ) ;
2026-05-21 20:39:36 -07:00
assert . strictEqual ( rootPkg . engines . node , '>=20.18.1 <21 || >=22 <23' ) ;
2026-03-10 00:00:00 -07:00
assert . strictEqual ( rootPkg . scripts . test , 'npm run build && npm run test:src && npm run test:cli' ) ;
} ) ;
} ) ;