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.
		
		
		
		
		
			
		
			
				
	
	
		
			186 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			186 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			TypeScript
		
	
| 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 return empty result if WHATSAPP_COMMUNITY_ID is missing', async () => {
 | |
|       process.env.WHATSAPP_COMMUNITY_ID = '';
 | |
|       
 | |
|       const result = await GroupSyncService.syncGroups();
 | |
|       expect(result).toEqual({ added: 0, updated: 0 });
 | |
|     });
 | |
| 
 | |
|     it('should filter groups by community ID', async () => {
 | |
|       // Reset last sync attempt to force a sync
 | |
|       GroupSyncService['lastSyncAttempt'] = 0;
 | |
|       
 | |
|       const result = await GroupSyncService.syncGroups();
 | |
|       
 | |
|       // Verify the correct groups were processed
 | |
|       expect(result).toEqual({
 | |
|         added: 1,  // group1 should be added
 | |
|         updated: 0 // no existing groups to update
 | |
|       });
 | |
|       
 | |
|       // Verify database state
 | |
|       const groups = testDb.query('SELECT * FROM groups').all();
 | |
|       expect(groups).toHaveLength(1);
 | |
|       
 | |
|       const group = groups[0];
 | |
|       expect(group.id).toBe('group1');
 | |
|       expect(group.community_id).toBe('test-community');
 | |
|       expect(group.name).toBe('Group 1');
 | |
|       expect(group.active).toBe(1);
 | |
|       expect(group.last_verified).toBeTruthy();
 | |
|       
 | |
|       // Verify only the matching group was processed
 | |
|       expect(fetchMock).toHaveBeenCalledTimes(1);
 | |
|     });
 | |
| 
 | |
|     it('should update existing groups', 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: 0,
 | |
|         updated: 1
 | |
|       });
 | |
| 
 | |
|       const group = testDb.query('SELECT * FROM groups WHERE id = ?').get('group1');
 | |
|       expect(group).toEqual({
 | |
|         id: 'group1',
 | |
|         community_id: 'test-community',
 | |
|         name: 'Group 1',
 | |
|         active: 1,
 | |
|         last_verified: expect.any(String)
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     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({
 | |
|         id: 'old-group',
 | |
|         community_id: 'test-community',
 | |
|         name: 'Old Group',
 | |
|         active: 0,
 | |
|         last_verified: expect.any(String)
 | |
|       });
 | |
|       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);
 | |
|     });
 | |
|   });
 | |
| });
 |