import { describe, it, expect, afterAll } from 'bun:test'; import Database from 'bun:sqlite'; import { startWebServer } from './helpers/server'; import { createTempDb } from './helpers/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('API - /api/integrations/feeds', () => { const PORT = 19123; const BASE = `http://127.0.0.1:${PORT}`; const USER = '34600123456'; const GROUP = '123@g.us'; const SID = 'sid-test-123'; const tmp = createTempDb(); const db: any = tmp.db as Database; // Sembrar datos mínimos db.exec(`INSERT OR IGNORE INTO users (id) VALUES ('${USER}')`); db.exec(`INSERT OR IGNORE INTO groups (id, community_id, name, active) VALUES ('${GROUP}', 'comm1', 'Group 1', 1)`); db.exec(`INSERT OR IGNORE INTO allowed_groups (group_id, status, discovered_at, updated_at) VALUES ('${GROUP}', 'allowed', '${toIsoSql()}', '${toIsoSql()}')`); db.exec(`INSERT OR IGNORE INTO group_members (group_id, user_id, is_admin, is_active, first_seen_at, last_seen_at) VALUES ('${GROUP}', '${USER}', 0, 1, '${toIsoSql()}', '${toIsoSql()}')`); // Crear sesión web válida (cookie sid) const sidHashPromise = sha256Hex(SID); const serverPromise = startWebServer({ port: PORT, env: { DB_PATH: tmp.path, WEB_BASE_URL: BASE } }); let server: Awaited | null = null; afterAll(async () => { try { await server?.stop(); } catch {} try { tmp.cleanup(); } catch {} }); it('GET: autogenera y devuelve URLs para personal, grupo y aggregate; POST rotate rota el de grupo', async () => { server = await serverPromise; const sidHash = await sidHashPromise; // Insertar sesión después de lanzar el server (mismo archivo) db.exec(` INSERT OR REPLACE INTO web_sessions (id, user_id, session_hash, created_at, last_seen_at, expires_at) VALUES ('sess-1', '${USER}', '${sidHash}', '${toIsoSql()}', '${toIsoSql()}', '${toIsoSql(new Date(Date.now() + 2 * 60 * 60 * 1000))}') `); // GET feeds const res = await fetch(`${BASE}/api/integrations/feeds`, { headers: { cookie: `sid=${SID}` } }); expect(res.status).toBe(200); const body = await res.json(); // Personal URL presente (recién creada) expect(typeof body.personal).toBe('object'); expect(typeof body.personal.url === 'string' && body.personal.url.endsWith('.ics')).toBe(true); // Aggregate URL presente (recién creada) expect(typeof body.aggregate).toBe('object'); expect(typeof body.aggregate.url === 'string' && body.aggregate.url.endsWith('.ics')).toBe(true); // Grupo autogenerado con URL presente const groupFeed = (body.groups || []).find((g: any) => g.groupId === GROUP); expect(groupFeed).toBeDefined(); expect(typeof groupFeed.url === 'string' && groupFeed.url.endsWith('.ics')).toBe(true); const previousGroupUrl = groupFeed.url; // POST rotate para el grupo const resRotate = await fetch(`${BASE}/api/integrations/feeds/rotate`, { method: 'POST', headers: { 'content-type': 'application/json', cookie: `sid=${SID}` }, body: JSON.stringify({ type: 'group', groupId: GROUP }) }); expect(resRotate.status).toBe(200); const bodyRotate = await resRotate.json(); expect(typeof bodyRotate.url === 'string' && bodyRotate.url.endsWith('.ics')).toBe(true); expect(bodyRotate.url).not.toBe(previousGroupUrl); }); it('el feed de grupo devuelve 410 cuando el grupo está archivado y 200 cuando está activo', async () => { server = await serverPromise; const sidHash = await sidHashPromise; // Asegurar sesión db.exec(` INSERT OR REPLACE INTO web_sessions (id, user_id, session_hash, created_at, last_seen_at, expires_at) VALUES ('sess-2', '${USER}', '${sidHash}', '${toIsoSql()}', '${toIsoSql()}', '${toIsoSql(new Date(Date.now() + 2 * 60 * 60 * 1000))}') `); // Obtener URL de grupo const res = await fetch(`${BASE}/api/integrations/feeds`, { headers: { cookie: `sid=${SID}` } }); expect(res.status).toBe(200); const body = await res.json(); const groupFeed = (body.groups || []).find((g: any) => g.groupId === GROUP); expect(groupFeed).toBeDefined(); // Activo: debe devolver 200 const ok1 = await fetch(groupFeed.url); expect(ok1.status).toBe(200); // Archivar grupo y verificar 410 db.exec(`UPDATE groups SET archived = 1 WHERE id = '${GROUP}'`); const gone = await fetch(groupFeed.url); expect(gone.status).toBe(410); }); });