You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			126 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			126 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			TypeScript
		
	
| import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
 | |
| import Database from 'bun:sqlite';
 | |
| import { initializeDatabase, ensureUserExists } from '../../../src/db';
 | |
| import { GroupSyncService } from '../../../src/services/group-sync';
 | |
| import { ResponseQueue } from '../../../src/services/response-queue';
 | |
| 
 | |
| const envBackup = { ...process.env };
 | |
| let originalSyncMembers: any;
 | |
| let originalSyncGroups: any;
 | |
| 
 | |
| function sleep(ms: number) {
 | |
|   return new Promise(res => setTimeout(res, ms));
 | |
| }
 | |
| 
 | |
| describe('GroupSyncService - scheduler de miembros', () => {
 | |
|   beforeEach(() => {
 | |
|     originalSyncMembers = GroupSyncService.syncMembersForActiveGroups;
 | |
|     originalSyncGroups = GroupSyncService.syncGroups;
 | |
|   });
 | |
| 
 | |
|   afterEach(() => {
 | |
|     GroupSyncService.stopMembersScheduler();
 | |
|     GroupSyncService.stopGroupsScheduler();
 | |
|     GroupSyncService.syncMembersForActiveGroups = originalSyncMembers;
 | |
|     GroupSyncService.syncGroups = originalSyncGroups;
 | |
|     process.env = envBackup;
 | |
|   });
 | |
| 
 | |
|   test('no arranca en entorno de test', async () => {
 | |
|     process.env = { ...envBackup, NODE_ENV: 'test' };
 | |
|     let called = 0;
 | |
|     GroupSyncService.syncMembersForActiveGroups = async () => {
 | |
|       called++;
 | |
|       return { groups: 0, added: 0, updated: 0, deactivated: 0 };
 | |
|     };
 | |
| 
 | |
|     GroupSyncService.startMembersScheduler();
 | |
|     await sleep(100);
 | |
|     expect(called).toBe(0);
 | |
|   });
 | |
| 
 | |
|   test('arranca en producción y ejecuta según intervalo', async () => {
 | |
|     process.env = { ...envBackup, NODE_ENV: 'production', GROUP_MEMBERS_SYNC_INTERVAL_MS: '30' };
 | |
|     let called = 0;
 | |
|     GroupSyncService.syncMembersForActiveGroups = async () => {
 | |
|       called++;
 | |
|       return { groups: 0, added: 0, updated: 0, deactivated: 0 };
 | |
|     };
 | |
| 
 | |
|     GroupSyncService.startMembersScheduler();
 | |
|     await sleep(120);
 | |
|     GroupSyncService.stopMembersScheduler();
 | |
|     expect(called).toBeGreaterThanOrEqual(1);
 | |
|   });
 | |
| 
 | |
|   test('groups scheduler no arranca en entorno de test', async () => {
 | |
|     process.env = { ...envBackup, NODE_ENV: 'test' };
 | |
|     let called = 0;
 | |
|     GroupSyncService.syncGroups = async () => {
 | |
|       called++;
 | |
|       return { added: 0, updated: 0 };
 | |
|     };
 | |
| 
 | |
|     GroupSyncService.startGroupsScheduler();
 | |
|     await sleep(100);
 | |
|     expect(called).toBe(0);
 | |
|     expect(GroupSyncService.getSecondsUntilNextGroupSync()).toBeNull();
 | |
|   });
 | |
| 
 | |
|   test('groups scheduler arranca en producción y programa next tick', async () => {
 | |
|     process.env = { ...envBackup, NODE_ENV: 'production', GROUP_SYNC_INTERVAL_MS: '30' };
 | |
|     let called = 0;
 | |
|     GroupSyncService.syncGroups = async () => {
 | |
|       called++;
 | |
|       return { added: 0, updated: 0 };
 | |
|     };
 | |
| 
 | |
|     GroupSyncService.startGroupsScheduler();
 | |
|     const secs1 = GroupSyncService.getSecondsUntilNextGroupSync();
 | |
|     expect(secs1).not.toBeNull();
 | |
|     expect(Number(secs1)).toBeGreaterThan(0);
 | |
| 
 | |
|     await sleep(120);
 | |
|     GroupSyncService.stopGroupsScheduler();
 | |
|     expect(called).toBeGreaterThanOrEqual(1);
 | |
|   });
 | |
| 
 | |
|   test('al desactivarse un grupo en sync: revoca tokens, desactiva membresía y notifica admins', async () => {
 | |
|     process.env = { ...envBackup, NODE_ENV: 'development', ADMIN_USERS: '34600123456' };
 | |
| 
 | |
|     const memdb = new Database(':memory:');
 | |
|     initializeDatabase(memdb);
 | |
|     (GroupSyncService as any).dbInstance = memdb;
 | |
|     (ResponseQueue as any).dbInstance = memdb;
 | |
| 
 | |
|     // Sembrar grupo activo con miembro y token de calendario
 | |
|     memdb.exec(`INSERT INTO groups (id, community_id, name, active, archived, last_verified) VALUES ('g1@g.us','comm-1','G1',1,0,strftime('%Y-%m-%d %H:%M:%f','now'))`);
 | |
|     const uid = ensureUserExists('34600123456', memdb)!;
 | |
|     memdb.exec(`INSERT INTO group_members (group_id, user_id, is_admin, is_active, first_seen_at, last_seen_at) VALUES ('g1@g.us','${uid}',0,1,strftime('%Y-%m-%d %H:%M:%f','now'),strftime('%Y-%m-%d %H:%M:%f','now'))`);
 | |
|     memdb.exec(`INSERT INTO calendar_tokens (type, user_id, group_id, token_hash, created_at) VALUES ('group','${uid}','g1@g.us','h1',strftime('%Y-%m-%d %H:%M:%f','now'))`);
 | |
| 
 | |
|     // Stub: API devuelve 0 grupos → el existente pasa a inactivo
 | |
|     const originalFetch = (GroupSyncService as any).fetchGroupsFromAPI;
 | |
|     (GroupSyncService as any).fetchGroupsFromAPI = async () => [];
 | |
| 
 | |
|     try {
 | |
|       await GroupSyncService.syncGroups(true);
 | |
|     } finally {
 | |
|       // Restaurar stub
 | |
|       (GroupSyncService as any).fetchGroupsFromAPI = originalFetch;
 | |
|     }
 | |
| 
 | |
|     // Tokens revocados
 | |
|     const tok = memdb.query(`SELECT revoked_at FROM calendar_tokens WHERE group_id='g1@g.us'`).get() as any;
 | |
|     expect(tok && tok.revoked_at).toBeTruthy();
 | |
| 
 | |
|     // Membresías desactivadas
 | |
|     const mem = memdb.query(`SELECT is_active FROM group_members WHERE group_id='g1@g.us'`).get() as any;
 | |
|     expect(Number(mem?.is_active || 0)).toBe(0);
 | |
| 
 | |
|     // Notificación encolada a admins
 | |
|     const msg = memdb.query(`SELECT message FROM response_queue ORDER BY id DESC LIMIT 1`).get() as any;
 | |
|     expect(msg && String(msg.message)).toContain('/admin archivar-grupo g1@g.us');
 | |
|   });
 | |
| });
 |