import { MaterialsToDeleteDAO } from '../../../src/main/dao/materials-to-delete.dao'; import { DatabaseService } from '../../../src/main/services/database.service'; // Mock LoggerService to avoid Electron app dependency jest.mock('../../../src/main/services/logger.service', () => ({ LoggerService: { info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn(), }, })); describe('MaterialsToDeleteDAO', () => { let dao: MaterialsToDeleteDAO; let mockDbService: jest.Mocked; beforeEach(() => { // Clear all mocks before each test jest.clearAllMocks(); // Create a mock DatabaseService mockDbService = { executeSQLServerQuery: jest.fn(), } as any; dao = new MaterialsToDeleteDAO(mockDbService); }); describe('getMaterialsToDeleteByManagers', () => { it('should return all materials when managerNames is null', async () => { const mockResult = { rows: [ { material_name: 'MAT001' }, { material_name: 'MAT002' }, { material_name: 'MAT003' }, ], rowCount: 3, }; mockDbService.executeSQLServerQuery.mockResolvedValue(mockResult); const result = await dao.getMaterialsToDeleteByManagers(null); expect(result).toEqual(['MAT001', 'MAT002', 'MAT003']); expect(mockDbService.executeSQLServerQuery).toHaveBeenCalledTimes(1); // Verify the query does not contain WHERE clause const queryCall = mockDbService.executeSQLServerQuery.mock.calls[0]; const query = queryCall[0]; expect(query).not.toContain('WHERE'); expect(query).toContain('SELECT DISTINCT material_name'); expect(query).toContain('FROM materials_to_delete'); }); it('should return materials filtered by manager names using parameterized query', async () => { const managerNames = ['Manager1', 'Manager2']; const mockResult = { rows: [ { material_name: 'MAT001' }, { material_name: 'MAT002' }, ], rowCount: 2, }; mockDbService.executeSQLServerQuery.mockResolvedValue(mockResult); const result = await dao.getMaterialsToDeleteByManagers(managerNames); expect(result).toEqual(['MAT001', 'MAT002']); expect(mockDbService.executeSQLServerQuery).toHaveBeenCalledTimes(1); // Verify parameterized query is used (SQL injection protection) const queryCall = mockDbService.executeSQLServerQuery.mock.calls[0]; const query = queryCall[0]; const params = queryCall[1]; expect(query).toContain('WHERE manager_name IN'); expect(query).toContain('@param0'); expect(query).toContain('@param1'); // Verify parameters are passed separately (not concatenated in query) expect(params).toEqual(managerNames); // Ensure no string concatenation of values in query expect(query).not.toContain("'Manager1'"); expect(query).not.toContain("'Manager2'"); }); it('should return empty array when managerNames is empty', async () => { const result = await dao.getMaterialsToDeleteByManagers([]); expect(result).toEqual([]); expect(mockDbService.executeSQLServerQuery).not.toHaveBeenCalled(); }); it('should handle single manager name', async () => { const managerNames = ['Manager1']; const mockResult = { rows: [{ material_name: 'MAT001' }], rowCount: 1, }; mockDbService.executeSQLServerQuery.mockResolvedValue(mockResult); const result = await dao.getMaterialsToDeleteByManagers(managerNames); expect(result).toEqual(['MAT001']); expect(mockDbService.executeSQLServerQuery).toHaveBeenCalledTimes(1); const queryCall = mockDbService.executeSQLServerQuery.mock.calls[0]; expect(queryCall[0]).toContain('@param0'); expect(queryCall[1]).toEqual(['Manager1']); }); it('should return empty array when no materials found', async () => { const mockResult = { rows: [], rowCount: 0, }; mockDbService.executeSQLServerQuery.mockResolvedValue(mockResult); const result = await dao.getMaterialsToDeleteByManagers(['Manager1']); expect(result).toEqual([]); }); it('should handle SQL injection attempts via parameterized query', async () => { const maliciousInput = [ "Manager1'; DROP TABLE materials_to_delete; --", "Manager2' OR '1'='1", ]; const mockResult = { rows: [], rowCount: 0, }; mockDbService.executeSQLServerQuery.mockResolvedValue(mockResult); await dao.getMaterialsToDeleteByManagers(maliciousInput); // Verify parameters are passed as values, not concatenated const queryCall = mockDbService.executeSQLServerQuery.mock.calls[0]; const query = queryCall[0]; const params = queryCall[1]; // The malicious strings should be in params, not in query expect(params).toEqual(maliciousInput); // Query should only contain placeholders, not actual values expect(query).not.toContain('DROP TABLE'); expect(query).not.toContain('OR 1=1'); expect(query).toMatch(/@param\d+/); }); it('should throw error when database query fails', async () => { const dbError = new Error('Database connection failed'); mockDbService.executeSQLServerQuery.mockRejectedValue(dbError); await expect( dao.getMaterialsToDeleteByManagers(['Manager1']) ).rejects.toThrow('Database connection failed'); }); it('should handle large number of manager names', async () => { const managerNames = Array.from({ length: 100 }, (_, i) => `Manager${i}`); const mockResult = { rows: [{ material_name: 'MAT001' }], rowCount: 1, }; mockDbService.executeSQLServerQuery.mockResolvedValue(mockResult); const result = await dao.getMaterialsToDeleteByManagers(managerNames); expect(result).toEqual(['MAT001']); // Verify all parameters are passed const queryCall = mockDbService.executeSQLServerQuery.mock.calls[0]; expect(queryCall[1]).toEqual(managerNames); expect(queryCall[1]?.length).toBe(100); }); }); describe('getAllMaterialsToDelete', () => { it('should return all materials by calling getMaterialsToDeleteByManagers with null', async () => { const mockResult = { rows: [ { material_name: 'MAT001' }, { material_name: 'MAT002' }, { material_name: 'MAT003' }, ], rowCount: 3, }; mockDbService.executeSQLServerQuery.mockResolvedValue(mockResult); const result = await dao.getAllMaterialsToDelete(); expect(result).toEqual(['MAT001', 'MAT002', 'MAT003']); expect(mockDbService.executeSQLServerQuery).toHaveBeenCalledTimes(1); // Verify query without filter const queryCall = mockDbService.executeSQLServerQuery.mock.calls[0]; expect(queryCall[0]).not.toContain('WHERE'); }); it('should propagate errors from getMaterialsToDeleteByManagers', async () => { const dbError = new Error('Database error'); mockDbService.executeSQLServerQuery.mockRejectedValue(dbError); await expect(dao.getAllMaterialsToDelete()).rejects.toThrow( 'Database error' ); }); it('should return empty array when no materials exist', async () => { const mockResult = { rows: [], rowCount: 0, }; mockDbService.executeSQLServerQuery.mockResolvedValue(mockResult); const result = await dao.getAllMaterialsToDelete(); expect(result).toEqual([]); }); }); });