feat: add production deployment and runtime safeguards

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Gustavo
2026-09-28 15:48:44 -03:00
co-authored by Claude Sonnet 5
parent 8ce2fcdfea
commit a9ef4d776d
23 changed files with 1696 additions and 8 deletions
+39
View File
@@ -177,6 +177,33 @@ async function renderTreatment(app, rows) {
return app.element('tabela-leads-tbody');
}
test('docker deployment files preserve persistent data and do not include secrets', async () => {
const dockerfile = await read('Dockerfile');
const compose = await read('docker-compose.yml');
const envExample = await read('.env.production.example');
const caddy = await read('Caddyfile.example');
assert.match(dockerfile, /FROM node:24/);
assert.match(compose, /restart: unless-stopped/);
assert.match(compose, /APP_HOST=0\.0\.0\.0/);
assert.match(compose, /APP_DB_PATH=\/app\/data\/maquina-pessoal\.db/);
assert.match(compose, /APP_WHATSAPP_AUTH_DIR=\/app\/data\/runtime\/whatsapp-auth/);
assert.match(caddy, /reverse_proxy app:3910/);
assert.doesNotMatch(envExample, /[0-9]{5,}:[A-Za-z0-9_-]{20,}/);
});
test('oracle deployment runbook changes default admin password before public ingress', async () => {
const runbook = await read('docs/deployment/oracle-free-docker.md');
const firstPrivateStep = runbook.indexOf('Do not open `80/tcp`, `443/tcp`, or public DNS yet.');
const passwordChangeStep = runbook.indexOf('Open **Configuracoes > Alterar minha senha**');
const publicIngressStep = runbook.indexOf('open OCI ingress for `80/tcp` and `443/tcp` from the internet');
assert.ok(firstPrivateStep > -1);
assert.ok(passwordChangeStep > firstPrivateStep);
assert.ok(publicIngressStep > passwordChangeStep);
assert.match(runbook, /127\.0\.0\.1:3910:3910/);
});
test('interface e arquivos operacionais usam somente a identidade pessoal', async () => {
const files = await Promise.all([
'public/index.html',
@@ -231,6 +258,18 @@ test('fontes usam os nomes de ambiente documentados', async () => {
assert.doesNotMatch(placesText, /GOOGLE_MAPS_API_KEY/);
});
test('env exposes APP_HOST with local-safe fallback', async () => {
const previous = process.env.APP_HOST;
process.env.APP_HOST = '0.0.0.0';
try {
const env = await import(`../src/env.mjs?host-test=${Date.now()}`);
assert.equal(env.APP_HOST, '0.0.0.0');
} finally {
if (previous === undefined) delete process.env.APP_HOST;
else process.env.APP_HOST = previous;
}
});
test('catalogo preserva segmentos com um servico neutro por segmento', async () => {
const config = JSON.parse(await read('prospeccao/config.json'));
assert.ok(config.segments.length > 0);