teqo/tests/unit/deployScript.unit.spec.ts
Francisco Solla 23c4c7b093
Some checks failed
PR Ready + auto-merge / ready-automerge (pull_request) Failing after 1s
CI (PR) / scope (pull_request) Failing after 1s
CI (PR) / lint (pull_request) Failing after 1s
CI (PR) / format (pull_request) Failing after 1s
CI (PR) / docs-guards (pull_request) Failing after 1s
CI (PR) / plans-only-closes (pull_request) Failing after 1s
CI (PR) / typecheck (pull_request) Has been skipped
CI (PR) / build (pull_request) Has been skipped
CI (PR) / e2e (1) (pull_request) Has been skipped
CI (PR) / knip (pull_request) Has been skipped
CI (PR) / cycles (pull_request) Has been skipped
CI (PR) / unit (pull_request) Has been skipped
CI (PR) / int (pull_request) Has been skipped
CI (PR) / e2e (2) (pull_request) Has been skipped
CI (PR) / checks (pull_request) Failing after 2s
ops(OPS65): hardening pós-review do gate — revision do container como verdade + gate vermelho em erro de classificação
- gate lê label org.opencontainers.image.revision do container rodando
  (compose pode mentir após rollback falho); desconhecido → fail-open verde;
  incapaz de classificar → exit 1 (run vermelho, nunca blackout silencioso)
- gate main-only (github.ref) + setup-node explícito; deploy guard usa a
  mesma revision (idempotência à prova de rollback-falho)
- docs: runbook (verificação ao vivo, falhas novas), AGENT-OPS (pool dormente),
  skills engineering-audit/model-selection, changelog; impl plan atualizado
2026-08-18 20:32:41 -03:00

79 lines
3.2 KiB
TypeScript

// @vitest-environment node
import { execFileSync } from 'node:child_process'
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
const repoRoot = join(fileURLToPath(new URL('.', import.meta.url)), '..', '..')
const scriptPath = join(repoRoot, 'scripts', 'deploy-homeserver.sh')
const script = readFileSync(scriptPath, 'utf8')
describe('scripts/deploy-homeserver.sh (OPS53 deploy pipeline)', () => {
it('is valid bash', () => {
expect(() => execFileSync('bash', ['-n', scriptPath], { stdio: 'pipe' })).not.toThrow()
})
it('guards against stale runs: only the current main HEAD deploys', () => {
expect(script).toContain('git ls-remote')
expect(script).toContain('refs/heads/main')
expect(script).toContain('stale run')
})
it('skips idempotently when the running container already runs the SHA (OPS65)', () => {
expect(script).toContain('already deployed')
expect(script).toContain('docker inspect')
expect(script).toContain('org.opencontainers.image.revision')
expect(script).toContain('$running_rev')
expect(script).toContain('"$running_rev" = "$SHA"')
})
it('builds with BuildKit secrets — prod credentials never enter image layers', () => {
expect(script).toContain('--secret')
expect(script).toContain('id=database_url,env=DATABASE_URL')
expect(script).toContain('id=payload_secret,env=PAYLOAD_SECRET')
})
it('reaches the prod DB from the build (loopback proxy on the compose network)', () => {
// BuildKit rejects `--network <bridge>`; the build uses --network host and
// a socat proxy (on stack_default, published on the host loopback) with a
// rewritten DATABASE_URL.
expect(script).toContain('--network host')
expect(script).toContain('teqo-1313-build-proxy')
expect(script).toContain('TCP:postgres:5432')
expect(script).toContain('127.0.0.1:5433')
})
it('applies migrations through the maintenance service before the rollout', () => {
// The rollback helper also contains a `compose up -d` (before the migrate
// step); the REAL rollout is the last occurrence.
const migrateIndex = script.indexOf('teqo-1313-migrate')
const upIndex = script.lastIndexOf('docker compose up -d teqo-1313')
expect(migrateIndex).toBeGreaterThan(-1)
expect(upIndex).toBeGreaterThan(-1)
expect(migrateIndex).toBeLessThan(upIndex)
})
it('smokes the deployed surface with the prod revalidate secret', () => {
for (const fragment of [
'/campanha/login',
'/admin',
'/campanha/webauthn/login-options',
'/api/revalidate',
'x-revalidate-secret',
]) {
expect(script, fragment).toContain(fragment)
}
})
it('never echoes secret values (no set -x, passwords only via stdin/secrets)', () => {
expect(script).not.toMatch(/^set -x\b/m)
expect(script).not.toContain('echo "$DATABASE_URL"')
expect(script).not.toContain('echo "$PAYLOAD_SECRET"')
// Registry password goes to docker via stdin — never stdout, never argv.
expect(script).toContain('echo "$REGISTRY_PASSWORD" | docker login')
expect(script).not.toContain('--password "$REGISTRY_PASSWORD"')
})
})