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/tasks', () => { const USER = '34600123456'; const OTHER = '34600999888'; const GROUP_OK = '123@g.us'; const GROUP_BAD = '999@g.us'; let dbPath: string; let server: Awaited> | null = null; let tmpDir: string; let sid = 'sid-test-tasks'; beforeAll(async () => { tmpDir = mkdtempSync(join(tmpdir(), 'webtest-')); dbPath = join(tmpDir, 'tasks.db'); const db = new Database(dbPath); initializeDatabase(db); // Asegurar usuarios const uid = ensureUserExists(USER, db)!; const oid = ensureUserExists(OTHER, db)!; // Sembrar grupos y gating db.prepare(` INSERT INTO groups (id, community_id, name, active, last_verified) VALUES (?, 'comm-1', 'Group OK', 1, strftime('%Y-%m-%d %H:%M:%f','now')) `).run(GROUP_OK); db.prepare(` INSERT INTO groups (id, community_id, name, active, last_verified) VALUES (?, 'comm-1', 'Group BAD', 1, strftime('%Y-%m-%d %H:%M:%f','now')) `).run(GROUP_BAD); db.prepare(`INSERT INTO allowed_groups (group_id, status, updated_at) VALUES (?, 'allowed', strftime('%Y-%m-%d %H:%M:%f','now'))`).run(GROUP_OK); db.prepare(`INSERT INTO allowed_groups (group_id, status, updated_at) VALUES (?, 'blocked', strftime('%Y-%m-%d %H:%M:%f','now'))`).run(GROUP_BAD); // Membresía activa solo en GROUP_OK const nowIso = toIsoSql(new Date()); db.prepare(` INSERT INTO group_members (group_id, user_id, is_admin, is_active, first_seen_at, last_seen_at, last_role_change_at) VALUES (?, ?, 0, 1, ?, ?, ?) `).run(GROUP_OK, uid, nowIso, nowIso, nowIso); // Tareas en GROUP_OK: una con due_date, otra sin due_date const insertTask = db.prepare(` INSERT INTO tasks (description, due_date, group_id, created_by, display_code) VALUES (?, ?, ?, ?, ?) `); const insertAssign = db.prepare(` INSERT INTO task_assignments (task_id, user_id, assigned_by) VALUES (?, ?, ?) `); const r1 = insertTask.run('alpha', '2025-01-02', GROUP_OK, uid, 101) as any; const t1 = Number(r1.lastInsertRowid); insertAssign.run(t1, uid, uid); const r2 = insertTask.run('beta_100% exacta', null, GROUP_OK, uid, 102) as any; const t2 = Number(r2.lastInsertRowid); insertAssign.run(t2, uid, uid); // Tarea en GROUP_BAD asignada al usuario (debe ser filtrada por gating) const r3 = insertTask.run('gamma filtrada', '2025-02-01', GROUP_BAD, oid, 103) as any; const t3 = Number(r3.lastInsertRowid); insertAssign.run(t3, uid, oid); // Crear sesión válida const hash = await sha256Hex(sid); db.prepare(` INSERT INTO web_sessions (session_hash, user_id, created_at, last_seen_at, expires_at) VALUES (?, ?, ?, ?, ?) `).run(hash, uid, nowIso, nowIso, toIsoSql(new Date(Date.now() + 60 * 60 * 1000))); db.close(); // Arrancar servidor server = await startWebServer({ port: 19101, env: { DB_PATH: dbPath, TZ: 'UTC' } }); }); afterAll(async () => { try { await server?.stop(); } catch {} try { rmSync(tmpDir, { recursive: true, force: true }); } catch {} }); it('aplica gating y ordena por due_date asc con NULL al final', async () => { const res = await fetch(`${server!.baseUrl}/api/me/tasks?limit=10`, { headers: { Cookie: `sid=${sid}` } }); expect(res.status).toBe(200); const json = await res.json(); const ids = json.items.map((it: any) => it.display_code ?? it.id); // Deben venir solo t1 (101) y t2 (102), en ese orden (t1 con fecha primero, luego NULL) expect(ids).toEqual([101, 102]); }); it('filtra por búsqueda escapando % y _ correctamente', async () => { const q = encodeURIComponent('beta_100%'); const res = await fetch(`${server!.baseUrl}/api/me/tasks?limit=10&search=${q}`, { headers: { Cookie: `sid=${sid}` } }); expect(res.status).toBe(200); const json = await res.json(); expect(json.items.length).toBe(1); expect(json.items[0].description).toContain('beta_100%'); }); });