import { describe, it, expect, beforeEach, afterAll, mock } from 'bun:test'; import { GroupSyncService } from '../../../src/services/group-sync'; import { db } from '../../../src/db'; import { Database } from 'bun:sqlite'; import { initializeDatabase } from '../../../src/db'; // Store original globals const originalFetch = globalThis.fetch; const originalConsoleError = console.error; describe('GroupSyncService', () => { let fetchMock: any; let testDb: Database; beforeEach(() => { // Create a new in-memory database for each test testDb = new Database(':memory:'); initializeDatabase(testDb); // Assign the isolated DB to the service GroupSyncService.dbInstance = testDb; // Clear and reset test data testDb.exec('DELETE FROM groups'); testDb.exec('DELETE FROM sqlite_sequence WHERE name="groups"'); GroupSyncService['lastSyncAttempt'] = 0; // Setup mock fetch to return proper Response object fetchMock = mock(async () => { const groups = [ { id: 'group1', subject: 'Group 1', linkedParent: 'test-community' }, { id: 'group2', subject: 'Group 2', linkedParent: 'other-community' }, { id: 'group3', subject: 'Group 3' } // No linkedParent ]; return { ok: true, status: 200, statusText: 'OK', headers: new Headers({'Content-Type': 'application/json'}), text: async () => JSON.stringify(groups), json: async () => groups }; }); globalThis.fetch = fetchMock; // Setup env vars process.env.WHATSAPP_COMMUNITY_ID = 'test-community'; process.env.EVOLUTION_API_URL = 'http://test-api'; process.env.EVOLUTION_API_INSTANCE = 'test-instance'; process.env.EVOLUTION_API_KEY = 'test-key'; }); afterAll(() => { globalThis.fetch = originalFetch; }); describe('syncGroups', () => { it('should skip sync if called too soon', async () => { GroupSyncService['lastSyncAttempt'] = Date.now() - 1000; const result = await GroupSyncService.syncGroups(); expect(result).toEqual({ added: 0, updated: 0 }); expect(fetchMock).not.toHaveBeenCalled(); }); it('should upsert all groups even if WHATSAPP_COMMUNITY_ID is missing (multicommunity)', async () => { process.env.WHATSAPP_COMMUNITY_ID = ''; const result = await GroupSyncService.syncGroups(); expect(result).toEqual({ added: 3, updated: 0 }); }); it('should upsert all groups regardless of community ID (multicommunity)', async () => { // Reset last sync attempt to force a sync GroupSyncService['lastSyncAttempt'] = 0; const result = await GroupSyncService.syncGroups(); // Verify all groups were processed expect(result).toEqual({ added: 3, updated: 0 }); // Verify database state const groups = testDb.query('SELECT id, community_id, name, active FROM groups').all() as any[]; expect(groups).toHaveLength(3); const map = new Map(groups.map((g: any) => [g.id, g])); expect(map.get('group1')).toEqual({ id: 'group1', community_id: 'test-community', name: 'Group 1', active: 1 }); expect(map.get('group2')).toEqual({ id: 'group2', community_id: 'other-community', name: 'Group 2', active: 1 }); expect(map.get('group3')).toEqual({ id: 'group3', community_id: '', name: 'Group 3', active: 1 }); // Verify API was called once expect(fetchMock).toHaveBeenCalledTimes(1); }); it('should update existing groups and insert new ones (multicommunity)', async () => { // Add initial group testDb.exec( "INSERT INTO groups (id, community_id, name, active) VALUES ('group1', 'test-community', 'Old Name', 1)" ); const result = await GroupSyncService.syncGroups(); expect(result).toEqual({ added: 2, updated: 1 }); const all = testDb.query('SELECT id, community_id, name, active FROM groups ORDER BY id').all(); expect(all).toEqual([ { id: 'group1', community_id: 'test-community', name: 'Group 1', active: 1 }, { id: 'group2', community_id: 'other-community', name: 'Group 2', active: 1 }, { id: 'group3', community_id: '', name: 'Group 3', active: 1 } ]); }); it('should mark non-matching groups as inactive', async () => { // Add initial group not in current sync testDb.exec( "INSERT INTO groups (id, community_id, name, active, last_verified) VALUES ('old-group', 'test-community', 'Old Group', 1, '2023-01-01')" ); await GroupSyncService.syncGroups(); const group = testDb.query('SELECT * FROM groups WHERE id = ?').get('old-group'); expect(group).toEqual(expect.objectContaining({ id: 'old-group', community_id: 'test-community', name: 'Old Group', active: 0, last_verified: expect.any(String), onboarding_prompted_at: null })); expect(group.last_verified).not.toBe('2023-01-01'); // Should be updated }); it('should handle API errors', async () => { const consoleErrorMock = mock(() => {}); console.error = consoleErrorMock; globalThis.fetch = mock(async () => ({ ok: false, status: 404, statusText: 'Not Found', headers: new Headers(), text: async () => 'Not Found', json: async () => ({ error: 'Not Found' }) })); await expect(GroupSyncService.syncGroups()).rejects.toThrow('API request failed: 404 Not Found'); expect(consoleErrorMock).toHaveBeenCalled(); console.error = originalConsoleError; }); }); describe('isGroupActive', () => { beforeEach(() => { // Clear cache and add test groups GroupSyncService.activeGroupsCache.clear(); testDb.exec('DELETE FROM groups'); testDb.exec("INSERT INTO groups (id, community_id, name, active) VALUES ('active-group', 'test-community', 'Active Group', 1)"); testDb.exec("INSERT INTO groups (id, community_id, name, active) VALUES ('inactive-group', 'test-community', 'Inactive Group', 0)"); // Populate cache GroupSyncService['cacheActiveGroups'](); }); it('should return true for active groups', () => { const result = GroupSyncService.isGroupActive('active-group'); expect(result).toBe(true); }); it('should return false for inactive groups', () => { const result = GroupSyncService.isGroupActive('inactive-group'); expect(result).toBe(false); }); it('should return false for non-existent groups', () => { const result = GroupSyncService.isGroupActive('non-existent-group'); expect(result).toBe(false); }); }); });