import { getDb } from '../../src/db/client.js' /** * Single shared sql client used by tests. Same singleton the services use, since * setup.js has already rewritten DATABASE_URL to point at the test schema. */ export const db = () => getDb() /** * Truncate Phase 3.7-relevant tables between tests. * * Order matters: pairing_failures FK → payment_sessions; chat_request_notifications * FK → chat_sessions; customer_transactions FK → chat_sessions; etc. Use CASCADE so * we don't have to maintain the topological order when tables get added. * * We deliberately do NOT truncate roles / control_center_users / mitras / customers * — those are seeded once per test file by fixtures and re-truncating them would * force every test to re-create users (slow + noisy). */ const TRUNCATE_TABLES = [ 'pairing_failures', 'payment_sessions', 'chat_request_notifications', 'session_extensions', 'session_closures', 'session_sensitivity_log', 'chat_messages', 'customer_transactions', 'chat_sessions', 'auth_sessions', 'otp_requests', 'mitra_online_logs', 'mitra_online_status', ] export const resetDb = async () => { const sql = db() // RESTART IDENTITY is a no-op for UUID PKs but cheap; CASCADE handles any future FK additions. await sql.unsafe(`TRUNCATE TABLE ${TRUNCATE_TABLES.join(', ')} RESTART IDENTITY CASCADE`) } /** * Wipe the slow-changing tables too — call sparingly (a single test that needs to * verify "no users" semantics, or in afterAll teardown). */ export const resetDbHard = async () => { const sql = db() await sql.unsafe( `TRUNCATE TABLE ${TRUNCATE_TABLES.join(', ')}, mitras, customers, control_center_users, roles RESTART IDENTITY CASCADE` ) } /** * Drop and re-seed the configurable app_config rows back to their canonical defaults. * Tests that mutate config (e.g. flipping pricing_promotions.enabled) call this in * afterEach. * * Note: the first-session discount config no longer lives in app_config (Stage 5 * deleted those legacy keys). It now lives in the `pricing_promotions` table, which * is also reset here back to the seed defaults that match migrate.js + the * DEFAULT_DISCOUNT in pricing.service.js. */ export const resetAppConfig = async () => { const sql = db() // Restore the same defaults the migration sets. Using ON CONFLICT … DO UPDATE so a // test-mutated row gets clobbered back, not just left alone. const defaults = [ ['anonymity', { enabled: false }], ['max_customers_per_mitra', { value: 3 }], ['extension_timeout_seconds', { value: 60 }], ['early_end_mitra_enabled', { value: false }], ['early_end_customer_enabled', { value: false }], ['payment_session_timeout_minutes', { value: 20 }], ['returning_chat_confirmation_timeout_seconds', { value: 20 }], ['extension_default_action_on_timeout', { value: 'auto_approve' }], ['pairing_blast_timeout_seconds', { value: 60 }], ['three_minute_warning_enabled', { value: true }], ] for (const [key, value] of defaults) { await sql` INSERT INTO app_config (key, value, updated_at) VALUES (${key}, ${sql.json(value)}, NOW()) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW() ` } // Reset pricing_promotions to canonical Phase 4 defaults. The Stage 1 backfill // gates on "table empty" so we can't rely on migrate.js to restore values after // a test mutates them — this UPDATE is the test-side reset hook. await sql` UPDATE pricing_promotions SET enabled = true, actual_price_idr = 2000, gimmick_price_idr = 12000, duration_minutes = 12, modes = ${['chat']}, updated_at = NOW() WHERE eligibility = 'first_session' ` }