import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { DESTRUCTIVE_TEST_CONFIRMATION, assertDestructiveTestDatabase, describeDatabaseTarget, resolveDestructiveTestConfirmation, } from './lib/destructive-test-database-guard.js'; const LOCAL_URL = 'postgresql://postgres:postgres@127.0.0.1:54322/postgres'; const CONFIRMATION = DESTRUCTIVE_TEST_CONFIRMATION; function clientWithMarker(environment, allowDestructiveTests) { const queries = []; return { queries, client: { async query(sql) { queries.push(String(sql).trim().replace(/\s+/g, ' ')); return { rows: [{ environment, allowDestructiveTests }], rowCount: 1 }; }, }, }; } async function rejects(options, pattern) { await assert.rejects(() => assertDestructiveTestDatabase(options), pattern); } { const mock = clientWithMarker('local', true); const result = await assertDestructiveTestDatabase({ client: mock.client, databaseUrl: LOCAL_URL, confirmation: CONFIRMATION, operation: 'smoke seed', }); assert.equal(result.environment, 'local'); assert.equal(mock.queries.length, 1); assert.match(mock.queries[0], /^select environment,/i); } { const mock = clientWithMarker('ci', true); const result = await assertDestructiveTestDatabase({ client: mock.client, databaseUrl: 'postgresql://ci_runner:secret@postgres-ci.internal:6432/tiku_ci', confirmation: CONFIRMATION, }); assert.equal(result.environment, 'ci'); } for (const runtimeUser of ['tiku_api', 'tiku_worker']) { const mock = clientWithMarker('local', true); await rejects( { client: mock.client, databaseUrl: `postgresql://${runtimeUser}:prod-secret@127.0.0.1:5432/postgres`, confirmation: CONFIRMATION, }, /reserved for production runtime/, ); assert.equal(mock.queries.length, 0, 'known production targets must be rejected before SQL'); } { const missingMarkerClient = { query: async () => ({ rows: [], rowCount: 0 }) }; await rejects( { client: missingMarkerClient, databaseUrl: 'postgresql://postgres:secret@127.0.0.1:15432/postgres', confirmation: CONFIRMATION, }, /marker row is missing/, ); } for (const environment of ['production', 'staging']) { const mock = clientWithMarker(environment, true); await rejects( { client: mock.client, databaseUrl: LOCAL_URL, confirmation: CONFIRMATION }, new RegExp(`environment ${environment} is not approved`), ); } { const mock = clientWithMarker('local', false); await rejects( { client: mock.client, databaseUrl: LOCAL_URL, confirmation: CONFIRMATION }, /does not allow destructive tests/, ); } for (const confirmation of ['', 'wrong-confirmation']) { const mock = clientWithMarker('local', true); await rejects( { client: mock.client, databaseUrl: LOCAL_URL, confirmation }, /explicit confirmation/, ); assert.equal(mock.queries.length, 0, 'confirmation must be checked before SQL'); } { const queryErrorClient = { query: async () => { throw new Error('password=do-not-print'); } }; let error; try { await assertDestructiveTestDatabase({ client: queryErrorClient, databaseUrl: LOCAL_URL, confirmation: CONFIRMATION, }); } catch (caught) { error = caught; } assert.match(error?.message || '', /marker is unavailable or unreadable/); assert.equal(error.message.includes('do-not-print'), false); } for (const malformed of ['', 'not-a-url', 'https://example.com/database', 'postgresql://localhost']) { await rejects( { client: clientWithMarker('local', true).client, databaseUrl: malformed, confirmation: CONFIRMATION }, /Refusing destructive database test/, ); } { const target = describeDatabaseTarget('postgresql://user:super-secret@db.example.test:5439/tiku_test'); assert.deepEqual(target, { host: 'db.example.test', port: '5439', database: 'tiku_test', user: 'user', }); assert.equal(JSON.stringify(target).includes('super-secret'), false); assert.equal(resolveDestructiveTestConfirmation({}, ['--confirm', CONFIRMATION]), CONFIRMATION); assert.equal(resolveDestructiveTestConfirmation({ SMOKE_SEED_CONFIRM: CONFIRMATION }, []), CONFIRMATION); } const repoRoot = process.cwd(); const smokeSeed = fs.readFileSync(path.join(repoRoot, 'scripts', 'smoke-seed.js'), 'utf8'); const apiIntegration = fs.readFileSync(path.join(repoRoot, 'scripts', 'api-integration-test.js'), 'utf8'); const rlsTest = fs.readFileSync(path.join(repoRoot, 'scripts', 'rls-tenant-isolation-test.js'), 'utf8'); const autoBadgeConcurrency = fs.readFileSync( path.join(repoRoot, 'scripts', 'auto-badge-concurrency-test.js'), 'utf8', ); const migration = fs.readFileSync( path.join(repoRoot, 'supabase', 'migrations', '202607120001_destructive_test_environment_safety.sql'), 'utf8', ); const seed = fs.readFileSync(path.join(repoRoot, 'supabase', 'seed.sql'), 'utf8'); const packageJson = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')); for (const [name, source] of [ ['smoke seed', smokeSeed], ['API integration test', apiIntegration], ['RLS isolation test', rlsTest], ['auto badge concurrency test', autoBadgeConcurrency], ]) { assert.match(source, /assertDestructiveTestDatabase\s*\(/, `${name} must invoke the shared database guard`); } const guardCall = smokeSeed.indexOf('assertDestructiveTestDatabase('); const firstBegin = smokeSeed.search(/client\.query\(['"]begin['"]\)/i); const firstWrite = smokeSeed.search(/\b(?:update|delete from|insert into)\s+public\./i); assert.ok(guardCall >= 0, 'smoke seed must call the shared guard'); assert.ok(firstBegin > guardCall, 'smoke seed guard must run before BEGIN'); assert.ok(firstWrite > guardCall, 'smoke seed guard must run before persistent SQL writes'); assert.match(migration, /create table if not exists app_private\.environment_safety/i); assert.match(migration, /environment in \('local', 'test', 'ci', 'staging', 'production'\)/i); assert.match(migration, /allow_destructive_tests boolean not null default false/i); assert.doesNotMatch( migration, /insert into app_private\.environment_safety/i, 'migrations must not automatically authorize destructive tests', ); assert.match( seed, /insert into app_private\.environment_safety[\s\S]*values \(true, 'local', true\)/i, 'local Supabase seed must provision the local-only marker', ); assert.equal(packageJson.scripts?.['test:api:remote'], undefined, 'full API integration must not expose a remote mode'); for (const scriptName of ['db:smoke-seed:test', 'test:api', 'test:rls']) { assert.match( packageJson.scripts?.[scriptName] || '', new RegExp(DESTRUCTIVE_TEST_CONFIRMATION), `${scriptName} must carry the exact destructive-test confirmation`, ); } assert.match( packageJson.scripts?.['test:readiness'] || '', /auto-badge-concurrency-test\.js --confirm=SMOKE_SEED_LOCAL_OR_CI_ONLY/, 'readiness must explicitly confirm its destructive concurrency test', ); assert.ok( packageJson.scripts?.['test:readiness']?.includes('destructive-test-database-guard-test.js'), 'readiness contracts must include the destructive database guard test', ); console.log('[PASS] destructive test database fail-closed guard');