/** * Vitest test setup * * Miniflare-based D1 + KV simulation for integration tests. */ import { readFileSync } from 'fs'; import { join } from 'path'; import { beforeAll, afterEach } from 'vitest'; import { Miniflare } from 'miniflare'; let mf: Miniflare | null = null; let db: D1Database | null = null; declare global { var getMiniflareBindings: () => { DB: D1Database; RATE_LIMIT_KV: KVNamespace; SESSION_KV: KVNamespace; CACHE_KV: KVNamespace; }; } beforeAll(async () => { mf = new Miniflare({ modules: true, script: 'export default { fetch() { return new Response("test"); } }', d1Databases: { DB: '__test_db__', }, kvNamespaces: ['RATE_LIMIT_KV', 'SESSION_KV', 'CACHE_KV'], }); db = await mf.getD1Database('DB'); (global as any).getMiniflareBindings = () => ({ DB: db as D1Database, RATE_LIMIT_KV: {} as KVNamespace, SESSION_KV: {} as KVNamespace, CACHE_KV: {} as KVNamespace, }); // Schema initialization const schemaPath = join(__dirname, '../schema.sql'); const schema = readFileSync(schemaPath, 'utf-8'); const cleanSchema = schema .split('\n') .filter(line => !line.trim().startsWith('--')) .join('\n'); const statements = cleanSchema .split(';') .map(s => s.replace(/\s+/g, ' ').trim()) .filter(s => s.length > 0); try { for (const statement of statements) { await db.exec(statement + ';'); } } catch (error) { console.error('Schema initialization failed:', error); throw error; } }); afterEach(async () => { if (!db) return; // Delete child tables first, then parent tables (FK order) // 1. No FK dependencies between each other await db.exec('DELETE FROM feedback'); await db.exec('DELETE FROM pending_actions'); await db.exec('DELETE FROM audit_logs'); // 2. bank_notifications refs transactions await db.exec('DELETE FROM bank_notifications'); // 3. transactions refs users await db.exec('DELETE FROM transactions'); // 4. wallets refs users await db.exec('DELETE FROM wallets'); // 5. Asset tables ref users await db.exec('DELETE FROM domains'); await db.exec('DELETE FROM servers'); await db.exec('DELETE FROM services_ddos'); await db.exec('DELETE FROM services_vpn'); // 6. Conversation tables ref users await db.exec('DELETE FROM conversations'); await db.exec('DELETE FROM conversation_archives'); // 7. Standalone tables (no FKs) await db.exec('DELETE FROM knowledge_articles'); await db.exec('DELETE FROM d2_cache'); // 8. Session tables (no FKs to users) await db.exec('DELETE FROM onboarding_sessions'); await db.exec('DELETE FROM troubleshoot_sessions'); await db.exec('DELETE FROM asset_sessions'); await db.exec('DELETE FROM billing_sessions'); // 9. users last (parent table) await db.exec('DELETE FROM users'); }); /** * Create a test user and return its auto-incremented id. */ export async function createTestUser( telegramId: string, username?: string ): Promise { const bindings = getMiniflareBindings(); const result = await bindings.DB.prepare( 'INSERT INTO users (telegram_id, username) VALUES (?, ?)' ).bind(telegramId, username || null).run(); return Number(result.meta?.last_row_id || 0); } /** * Get the test D1Database binding. */ export function getTestDB(): D1Database { return getMiniflareBindings().DB; }