Assessor_2.0/tests/config/deployment-workflow.test.ts

81 lines
4.4 KiB
TypeScript

import { readFileSync } from 'node:fs';
import { describe, expect, it } from 'vitest';
const workflow = readFileSync(new URL('../../.gitea/workflows/deploy.yml', import.meta.url), 'utf8');
const compose = readFileSync(new URL('../../docker-compose.deploy.yml', import.meta.url), 'utf8');
describe('deployment workflow', () => {
it('maps main and experimental to isolated stacks and VPN ports', () => {
expect(workflow).toContain('ASSESSOR_ENV_FILE: ${{ secrets.PROD_ENV_FILE }}');
expect(workflow).toContain('ASSESSOR_ENV_FILE: ${{ secrets.STAGING_ENV_FILE }}');
expect(workflow).toContain('ASSESSOR_STACK=assessor_prod');
expect(workflow).toContain('ASSESSOR_STACK=assessor_experimental');
expect(workflow).toContain('ASSESSOR_PUBLISH_PORT=4173');
expect(workflow).toContain('ASSESSOR_PUBLISH_PORT=4174');
expect(workflow.match(/ASSESSOR_BIND=10\.0\.0\.154/g)).toHaveLength(2);
expect(workflow).not.toContain('assessor.soc-ops.de');
});
it('validates all database settings without printing secret values', () => {
expect(workflow.match(/if \[ -z "\$ASSESSOR_ENV_FILE" \]/g)).toHaveLength(2);
expect(workflow.match(/for key in ASSESSOR_POSTGRES_DB ASSESSOR_POSTGRES_USER ASSESSOR_POSTGRES_PASSWORD ASSESSOR_QDRANT_API_KEY/g)).toHaveLength(2);
expect(workflow).toContain('secret values were not printed.');
expect(workflow).not.toMatch(/echo "\$ASSESSOR_ENV_FILE"/);
expect(workflow).not.toContain('printf \'%s\\n\' "${{ secrets.');
});
it('tests, builds, validates and serializes each branch deployment', () => {
expect(workflow).toContain('run: npm test');
expect(workflow).toContain('run: npm run build');
expect(workflow).toMatch(/docker compose[^\n]*config --quiet/);
expect(workflow).toContain('publish=$ASSESSOR_PUBLISH_PORT');
expect(workflow).toContain('project="$(docker inspect');
expect(workflow).toContain('if [ "$project" != "$ASSESSOR_STACK" ]');
expect(workflow).toContain('name: Acquire environment deployment lock');
expect(workflow).toContain('name: Release environment deployment lock');
expect(workflow).toContain('${ASSESSOR_STACK}_deployment_lock');
expect(workflow).toContain('ASSESSOR_LOCK_ACQUIRED=true');
expect(workflow).toContain('if: always()');
expect(workflow).not.toMatch(/^concurrency:/m);
});
it('creates separate pre-deployment database and artifact backups', () => {
expect(workflow).toContain('backup_volume=assessor_deployment_backups');
expect(workflow).toContain('pg_dump --format=custom --no-owner');
expect(workflow).toContain('artifacts.tar.gz');
expect(workflow).toContain('SHA256SUMS');
expect(workflow).not.toContain('down --volumes');
expect(workflow).not.toContain('docker image prune');
expect(workflow).not.toMatch(/docker compose[^\n]*logs/);
});
it('checks application health inside the app container', () => {
expect(workflow).toMatch(/docker compose[^\n]*exec -T app/);
expect(workflow).toContain("fetch('http://127.0.0.1:4173/api/v1/health')");
expect(workflow).not.toMatch(/curl[^\n]*127\.0\.0\.1:4173/);
});
});
describe('deployment Compose stack', () => {
it('requires an explicit bind, port and database configuration', () => {
expect(compose).toContain('${ASSESSOR_BIND:?set ASSESSOR_BIND}');
expect(compose).toContain('${ASSESSOR_PUBLISH_PORT:?set ASSESSOR_PUBLISH_PORT}');
expect(compose).toContain('${ASSESSOR_POSTGRES_DB:?set ASSESSOR_POSTGRES_DB}');
expect(compose).toContain('${ASSESSOR_POSTGRES_USER:?set ASSESSOR_POSTGRES_USER}');
expect(compose).toContain('${ASSESSOR_POSTGRES_PASSWORD:?set ASSESSOR_POSTGRES_PASSWORD}');
expect(compose).toContain('${ASSESSOR_QDRANT_API_KEY:?set ASSESSOR_QDRANT_API_KEY}');
});
it('keeps PostgreSQL internal and scopes persistent volumes through Compose', () => {
const postgresSection = compose.slice(compose.indexOf(' postgres:'));
expect(postgresSection).not.toMatch(/\n ports:/);
expect(compose).toContain('assessor-artifacts:/var/lib/assessor/artifacts');
expect(compose).toContain('assessor-postgres-data:/var/lib/postgresql/data');
expect(compose.match(/restart: unless-stopped/g)).toHaveLength(3);
expect(compose).toContain('qdrant/qdrant:v1.19.1');
expect(compose).toContain('QDRANT__TELEMETRY_DISABLED: "true"');
expect(compose).toContain('assessor-qdrant-data:/qdrant/storage');
expect(compose.slice(compose.indexOf(' qdrant:'))).not.toMatch(/\n ports:/);
});
});