import { describe, it, expect, beforeAll, afterAll } from 'bun:test'; import { Database } from 'bun:sqlite'; import { mkdtempSync, rmSync } from 'fs'; import { tmpdir } from 'os'; import { join } from 'path'; import { startWebServer } from './helpers/server'; import { initializeDatabase, ensureUserExists } from '../../src/db'; async function sha256Hex(input: string): Promise { const enc = new TextEncoder().encode(input); const buf = await crypto.subtle.digest('SHA-256', enc); const bytes = new Uint8Array(buf); return Array.from(bytes) .map((b) => b.toString(16).padStart(2, '0')) .join(''); } function toIsoSql(d = new Date()): string { return d.toISOString().replace('T', ' ').replace('Z', ''); } describe('Web API - GET /api/me/preferences', () => { const userId = '34600123456'; let dbPath: string; let server: Awaited> | null = null; let tmpDir: string; beforeAll(async () => { tmpDir = mkdtempSync(join(tmpdir(), 'webtest-')); dbPath = join(tmpDir, 'tasks.db'); // Inicializar DB en archivo (como en prod) const db = new Database(dbPath); initializeDatabase(db); ensureUserExists(userId, db); // Crear sesión válida const sid = 'sid-test-pref'; const hash = await sha256Hex(sid); const now = new Date(); const nowIso = toIsoSql(now); const expIso = toIsoSql(new Date(now.getTime() + 60 * 60 * 1000)); // +1h db.prepare(` INSERT INTO web_sessions (session_hash, user_id, created_at, last_seen_at, expires_at) VALUES (?, ?, ?, ?, ?) `).run(hash, userId, nowIso, nowIso, expIso); db.close(); // Arrancar web apuntando a este DB server = await startWebServer({ port: 19100, env: { DB_PATH: dbPath, TZ: 'UTC' } }); // Probar que el endpoint responde (no asertivo aún) const res = await fetch(`${server.baseUrl}/api/me/preferences`, { headers: { Cookie: `sid=${sid}` } }); expect(res.status).toBe(200); }); afterAll(async () => { try { await server?.stop(); } catch {} try { rmSync(tmpDir, { recursive: true, force: true }); } catch {} }); it('devuelve valores por defecto cuando no hay preferencias guardadas', async () => { const sid = 'sid-test-pref'; const res = await fetch(`${server!.baseUrl}/api/me/preferences`, { headers: { Cookie: `sid=${sid}` } }); expect(res.status).toBe(200); const json = await res.json(); expect(json).toEqual({ freq: 'off', time: '08:30' }); }); it('POST /api/me/preferences - flujo completo', async () => { const sid = 'sid-test-pref'; const base = server!.baseUrl; // 1) daily sin hora -> usa default '08:30' let res = await fetch(`${base}/api/me/preferences`, { method: 'POST', headers: { 'content-type': 'application/json', Cookie: `sid=${sid}` }, body: JSON.stringify({ freq: 'daily' }) }); expect(res.status).toBe(200); let json = await res.json(); expect(json).toEqual({ freq: 'daily', time: '08:30' }); // GET refleja lo guardado res = await fetch(`${base}/api/me/preferences`, { headers: { Cookie: `sid=${sid}` } }); expect(res.status).toBe(200); json = await res.json(); expect(json).toEqual({ freq: 'daily', time: '08:30' }); // 2) weekly con hora '7:5' → normaliza a '07:05' res = await fetch(`${base}/api/me/preferences`, { method: 'POST', headers: { 'content-type': 'application/json', Cookie: `sid=${sid}` }, body: JSON.stringify({ freq: 'weekly', time: '7:5' }) }); expect(res.status).toBe(200); json = await res.json(); expect(json).toEqual({ freq: 'weekly', time: '07:05' }); // 3) weekdays con hora inválida → 400 res = await fetch(`${base}/api/me/preferences`, { method: 'POST', headers: { 'content-type': 'application/json', Cookie: `sid=${sid}` }, body: JSON.stringify({ freq: 'weekdays', time: '25:00' }) }); expect(res.status).toBe(400); // 4) freq inválida → 400 res = await fetch(`${base}/api/me/preferences`, { method: 'POST', headers: { 'content-type': 'application/json', Cookie: `sid=${sid}` }, body: JSON.stringify({ freq: 'foo', time: '08:00' }) }); expect(res.status).toBe(400); // 5) off sin hora → conserva última ('07:05') res = await fetch(`${base}/api/me/preferences`, { method: 'POST', headers: { 'content-type': 'application/json', Cookie: `sid=${sid}` }, body: JSON.stringify({ freq: 'off' }) }); expect(res.status).toBe(200); json = await res.json(); expect(json).toEqual({ freq: 'off', time: '07:05' }); // GET refleja off con hora conservada res = await fetch(`${base}/api/me/preferences`, { headers: { Cookie: `sid=${sid}` } }); expect(res.status).toBe(200); json = await res.json(); expect(json).toEqual({ freq: 'off', time: '07:05' }); // 6) weekdays con hora válida res = await fetch(`${base}/api/me/preferences`, { method: 'POST', headers: { 'content-type': 'application/json', Cookie: `sid=${sid}` }, body: JSON.stringify({ freq: 'weekdays', time: '18:45' }) }); expect(res.status).toBe(200); json = await res.json(); expect(json).toEqual({ freq: 'weekdays', time: '18:45' }); // 7) daily con hora '6:7' -> normaliza '06:07' res = await fetch(`${base}/api/me/preferences`, { method: 'POST', headers: { 'content-type': 'application/json', Cookie: `sid=${sid}` }, body: JSON.stringify({ freq: 'daily', time: '6:7' }) }); expect(res.status).toBe(200); json = await res.json(); expect(json).toEqual({ freq: 'daily', time: '06:07' }); // GET final res = await fetch(`${base}/api/me/preferences`, { headers: { Cookie: `sid=${sid}` } }); expect(res.status).toBe(200); json = await res.json(); expect(json).toEqual({ freq: 'daily', time: '06:07' }); }); });