import { Database } from 'bun:sqlite'; import { beforeAll, beforeEach, afterAll, describe, expect, test } from 'bun:test'; import { initializeDatabase } from '../../../src/db'; import { GroupSyncService } from '../../../src/services/group-sync'; describe('GroupSyncService - reconcileGroupMembers', () => { let memdb: Database; beforeAll(() => { memdb = new Database(':memory:'); memdb.exec('PRAGMA foreign_keys = ON;'); initializeDatabase(memdb); // Inyectar DB en el servicio GroupSyncService.dbInstance = memdb as any; }); afterAll(() => { memdb.close(); }); beforeEach(() => { // Limpiar tablas relevantes entre tests memdb.exec('DELETE FROM group_members'); memdb.exec('DELETE FROM users'); memdb.exec('DELETE FROM groups'); // Crear grupo base activo memdb.prepare(`INSERT INTO groups (id, community_id, name) VALUES (?, ?, ?)`) .run('123@g.us', 'community-1', 'Grupo 123'); }); function getMember(userId: string) { return memdb.prepare(` SELECT group_id, user_id, is_admin, is_active, first_seen_at, last_seen_at, last_role_change_at FROM group_members WHERE group_id = ? AND user_id = ? `).get('123@g.us', userId) as any; } test('inserta miembros y marca activos en la primera reconciliación', () => { const res = GroupSyncService.reconcileGroupMembers('123@g.us', [ { userId: '111', isAdmin: true }, { userId: '222', isAdmin: false } ], '2025-01-01 00:00:00.000'); expect(res).toEqual({ added: 2, updated: 0, deactivated: 0 }); const m1 = getMember('111'); const m2 = getMember('222'); expect(m1).toBeDefined(); expect(m1.is_active).toBe(1); expect(m1.is_admin).toBe(1); expect(m1.first_seen_at).toBe('2025-01-01 00:00:00.000'); expect(m1.last_seen_at).toBe('2025-01-01 00:00:00.000'); expect(m2).toBeDefined(); expect(m2.is_active).toBe(1); expect(m2.is_admin).toBe(0); }); test('actualiza roles, desactiva ausentes y añade nuevos en reconciliaciones posteriores', () => { // Primera pasada GroupSyncService.reconcileGroupMembers('123@g.us', [ { userId: '111', isAdmin: true }, { userId: '222', isAdmin: false } ], '2025-01-01 00:00:00.000'); // Segunda pasada con cambios: 111 pierde admin, 222 desaparece, 333 aparece const res2 = GroupSyncService.reconcileGroupMembers('123@g.us', [ { userId: '111', isAdmin: false }, { userId: '333', isAdmin: false } ], '2025-01-02 00:00:00.000'); expect(res2).toEqual({ added: 1, updated: 1, deactivated: 1 }); const m111 = getMember('111'); const m222 = getMember('222'); const m333 = getMember('333'); expect(m111.is_active).toBe(1); expect(m111.is_admin).toBe(0); expect(m111.last_role_change_at).toBe('2025-01-02 00:00:00.000'); expect(m222.is_active).toBe(0); expect(m333.is_active).toBe(1); expect(m333.is_admin).toBe(0); }); test('idempotencia: aplicar mismo snapshot no altera contadores y actualiza last_seen_at', () => { GroupSyncService.reconcileGroupMembers('123@g.us', [ { userId: '111', isAdmin: false }, { userId: '333', isAdmin: false } ], '2025-01-02 00:00:00.000'); const res3 = GroupSyncService.reconcileGroupMembers('123@g.us', [ { userId: '111', isAdmin: false }, { userId: '333', isAdmin: false } ], '2025-01-03 00:00:00.000'); expect(res3).toEqual({ added: 0, updated: 0, deactivated: 0 }); const m111 = getMember('111'); const m333 = getMember('333'); expect(m111.last_seen_at).toBe('2025-01-03 00:00:00.000'); expect(m333.last_seen_at).toBe('2025-01-03 00:00:00.000'); }); });