import { expect, test } from 'bun:test'; import { readFileSync } from 'node:fs'; const workflow = readFileSync('.gitea/workflows/ci.yml', 'utf8'); const compose = readFileSync('compose.yaml', 'utf8'); const dockerfile = readFileSync('Dockerfile', 'utf8'); const dockerignore = readFileSync('.dockerignore', 'utf8'); test('Gitea workflow separates PR source checks from trusted container builds', () => { expect(workflow).toContain('pull_request:'); expect(workflow).toContain("if: ${{ gitea.event_name == 'push' }}"); expect(workflow).toContain('RELEASE_ENV=staging'); expect(workflow).toContain('bun run test:release-gates'); expect(workflow).toContain('docker compose config --quiet'); expect(workflow).toContain('docker build --build-arg APP_RELEASE_ID=fixture-ci'); expect(workflow).toContain('--network none'); }); test('Compose and Docker defaults are isolated and digest-pinned', () => { expect(compose).toContain('CONVEX_BIND_ADDRESS:-127.0.0.1'); expect(compose).toContain('WEB_BIND_ADDRESS:-127.0.0.1'); expect(compose).toMatch(/CONVEX_IMAGE:-[^\n]*@sha256:[a-f0-9]{64}/); expect(compose).toMatch(/CONVEX_DASHBOARD_IMAGE:-[^\n]*@sha256:[a-f0-9]{64}/); const bases = [...dockerfile.matchAll(/^FROM\s+([^\s]+)/gim)].map((match) => match[1]); expect(bases.length).toBeGreaterThan(0); expect(bases.every((image) => /@sha256:[a-f0-9]{64}$/i.test(image))).toBe(true); for (const pattern of ['*.pem', '*.key', '*.p12', '*.pfx', 'credentials*', 'secrets*', '.npmrc']) { expect(dockerignore).toContain(pattern); } });