Files
backend/test/container-smoke.cjs

78 lines
3.5 KiB
JavaScript

// Real image boundary: catches missing native modules, proto assets and migrations.
const assert = require('node:assert/strict');
const { execFileSync } = require('node:child_process');
const { randomUUID } = require('node:crypto');
const image = process.argv[2];
assert.ok(image, 'Pass a built backend image reference');
const name = `wealthledger-smoke-${randomUUID()}`;
// eslint-disable-next-line sonarjs/no-os-command-from-path -- Developer-only command uses the operator PATH and fixed executable/arguments; no request input.
const docker = (...args) => execFileSync('docker', args, { encoding: 'utf8' }).trim();
const environment = [
'-e', 'DATABASE_URL=file:/data/wealthledger.db',
'-e', 'FRONTEND_ORIGIN=http://localhost',
'-e', 'JWT_SECRET=disposable-image-smoke-secret-at-least-32-characters',
'-e', 'TOKEN_ENCRYPTION_KEY=MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=',
'-e', 'LOG_LEVEL=error',
];
async function ready() {
for (let attempt = 0; attempt < 90; attempt++) {
const state = JSON.parse(docker('inspect', name, '--format', '{{json .State}}'));
assert.equal(state.Running, true, 'Image exited before readiness');
if (state.Health.Status === 'healthy') return;
await new Promise((resolve) => setTimeout(resolve, 1000));
}
throw new Error('Image readiness timed out');
}
(async () => {
docker('volume', 'create', name);
try {
assert.throws(() => docker('run', '--rm', '--read-only', image),
'Missing runtime configuration must prevent startup');
const migrate = () => docker('run', '--rm', '--read-only', '-v', `${name}:/data`,
...environment, image, 'node', 'node_modules/prisma/build/index.js', 'migrate', 'deploy');
migrate();
docker('run', '-d', '--name', name, '--read-only', '-v', `${name}:/data`, ...environment, image);
await ready();
assert.deepEqual(JSON.parse(docker('inspect', name, '--format', '{{json .HostConfig.PortBindings}}')), {});
docker('exec', name, 'node', '-e', `
const assert = require('node:assert/strict');
const fs = require('node:fs');
assert.notEqual(process.getuid(), 0);
assert.equal(fs.existsSync('src'), false);
assert.equal(fs.existsSync('.env'), false);
assert.equal(fs.existsSync('node_modules/typescript'), false);
assert.equal(fs.existsSync('node_modules/@nestjs/cli'), false);
fs.accessSync('dist/src/integrations/tbank/proto/contracts/users.proto');
fetch('http://localhost:3000/api/v1/auth/register', {
method: 'POST', headers: {'content-type':'application/json'},
body: JSON.stringify({email:'image-smoke@example.test',password:'fictional-image-smoke-password'})
}).then(async r => {assert.equal(r.status, 200); await r.json()}).catch(e => {console.error(e); process.exitCode=1});
`);
docker('stop', name);
migrate();
docker('start', name);
await ready();
docker('exec', name, 'node', '-e', `
const assert = require('node:assert/strict');
const db = require('better-sqlite3')('/data/wealthledger.db');
assert.equal(db.prepare('SELECT count(*) AS n FROM User').get().n, 1);
assert.equal(db.prepare('SELECT count(*) AS n FROM Session').get().n, 1);
assert.equal(db.pragma('integrity_check', {simple:true}), 'ok');
db.close();
`);
console.log('PASS image: fresh/existing migration, non-root read-only runtime, auth, persistence, proto assets');
} finally {
try {
docker('rm', '-f', name);
} finally {
docker('volume', 'rm', name);
}
}
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});