From 644a25905456fb6b53bbce07b726e3ade0934707 Mon Sep 17 00:00:00 2001 From: burgerni10 Date: Fri, 15 Nov 2024 19:05:28 +0100 Subject: [PATCH] fix(history-query): add history query tests and remove create full config message --- .../src/service/history-query.service.spec.ts | 772 +++++++++++++++++- backend/src/service/history-query.service.ts | 60 +- .../__mocks__/history-query-engine.mock.ts | 5 +- .../service/history-query-service.mock.ts | 2 +- .../oia/oianalytics-message-service.mock.ts | 3 +- backend/src/tests/utils/test-data.ts | 42 +- .../history-query.controller.spec.ts | 311 ++++++- .../controllers/history-query.controller.ts | 2 +- 8 files changed, 1152 insertions(+), 45 deletions(-) diff --git a/backend/src/service/history-query.service.spec.ts b/backend/src/service/history-query.service.spec.ts index 807578ceb2..679f248a2b 100644 --- a/backend/src/service/history-query.service.spec.ts +++ b/backend/src/service/history-query.service.spec.ts @@ -5,8 +5,8 @@ import HistoryQueryRepository from '../repository/config/history-query.repositor import HistoryQueryRepositoryMock from '../tests/__mocks__/repository/config/history-query-repository.mock'; import LogRepository from '../repository/logs/log.repository'; import LogRepositoryMock from '../tests/__mocks__/repository/log/log-repository.mock'; -import SouthService from './south.service'; -import NorthService from './north.service'; +import SouthService, { southManifestList } from './south.service'; +import NorthService, { northManifestList } from './north.service'; import pino from 'pino'; import SouthServiceMock from '../tests/__mocks__/service/south-service.mock'; import NorthServiceMock from '../tests/__mocks__/service/north-service.mock'; @@ -24,8 +24,11 @@ import testData from '../tests/utils/test-data'; import { mockBaseFolders } from '../tests/utils/test-utils'; import { BaseFolders } from '../model/types'; import fs from 'node:fs/promises'; - +import csv from 'papaparse'; +import multer from '@koa/multer'; +jest.mock('papaparse'); jest.mock('node:fs/promises'); +jest.mock('../web-server/controllers/validators/joi.validator'); const validator = new JoiValidator(); const logger: pino.Logger = new PinoLogger(); @@ -40,7 +43,7 @@ const encryptionService: EncryptionService = new EncryptionServiceMock('', ''); const historyQueryEngine: HistoryQueryEngine = new HistoryQueryEngineMock(logger); let service: HistoryQueryService; -describe('history query service', () => { +describe('History Query service', () => { beforeEach(() => { jest.clearAllMocks(); service = new HistoryQueryService( @@ -57,13 +60,138 @@ describe('history query service', () => { ); }); + it('testNorth() should test North settings in creation mode', async () => { + (northService.getInstalledNorthManifests as jest.Mock).mockReturnValueOnce([ + { + ...northManifestList[4] // file-writer + } + ]); + await service.testNorth('create', testData.north.command, logger); + expect(northService.testNorth).toHaveBeenCalledWith('create', testData.north.command, logger); + }); + + it('testNorth() should throw an error if manifest type is bad', async () => { + (northService.getInstalledNorthManifests as jest.Mock).mockReturnValueOnce([]); + const badCommand = JSON.parse(JSON.stringify(testData.north.command)); + badCommand.type = 'bad'; + await expect(service.testNorth('create', badCommand, logger)).rejects.toThrow('North manifest bad not found'); + expect(northService.testNorth).not.toHaveBeenCalled(); + }); + + it('testNorth() should test North connector in edit mode', async () => { + (northService.getInstalledNorthManifests as jest.Mock).mockReturnValueOnce([ + { + ...northManifestList[4] // file-writer + } + ]); + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0]); + await service.testNorth(testData.historyQueries.list[0].id, testData.north.command, logger); + expect(northService.testNorth).toHaveBeenCalled(); + }); + + it('testNorth() should fail to test North connector in edit mode if north connector not found', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(null); + await expect(service.testNorth(testData.historyQueries.list[0].id, testData.north.command, logger)).rejects.toThrow( + `History query ${testData.historyQueries.list[0].id} not found` + ); + expect(northService.testNorth).not.toHaveBeenCalled(); + }); + + it('testSouth() should test South settings in creation mode', async () => { + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...southManifestList[0] // folder-scanner + } + ]); + await service.testSouth('create', testData.south.command, logger); + expect(southService.testSouth).toHaveBeenCalledWith('create', testData.south.command, logger); + }); + + it('testSouth() should throw an error if manifest type is bad', async () => { + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([]); + const badCommand = JSON.parse(JSON.stringify(testData.south.command)); + badCommand.type = 'bad'; + await expect(service.testSouth('create', badCommand, logger)).rejects.toThrow('South manifest bad not found'); + expect(southService.testSouth).not.toHaveBeenCalled(); + }); + + it('testSouth() should test South connector in edit mode', async () => { + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...southManifestList[0] // folder-scanner + } + ]); + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0]); + await service.testSouth(testData.historyQueries.list[0].id, testData.south.command, logger); + expect(southService.testSouth).toHaveBeenCalled(); + }); + + it('testSouth() should fail to test South connector in edit mode if south connector not found', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(null); + await expect(service.testSouth(testData.historyQueries.list[0].id, testData.south.command, logger)).rejects.toThrow( + `History query ${testData.historyQueries.list[0].id} not found` + ); + expect(southService.testSouth).not.toHaveBeenCalled(); + }); + + it('testSouthItem() should test South settings in creation mode', async () => { + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...southManifestList[0] // folder-scanner + } + ]); + const callback = jest.fn(); + await service.testSouthItem('create', testData.south.command, testData.south.itemCommand, callback, logger); + expect(southService.testSouthItem).toHaveBeenCalledWith( + 'create', + testData.south.command, + { ...testData.south.itemCommand, scanModeId: 'history', scanModeName: null }, + callback, + logger + ); + }); + + it('testSouthItem() should throw an error if manifest type is bad', async () => { + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([]); + const badCommand = JSON.parse(JSON.stringify(testData.south.command)); + badCommand.type = 'bad'; + const callback = jest.fn(); + await expect(service.testSouthItem('create', badCommand, testData.south.itemCommand, callback, logger)).rejects.toThrow( + 'South manifest bad not found' + ); + + expect(southService.testSouthItem).not.toHaveBeenCalled(); + }); + + it('testSouthItem() should test South connector in edit mode', async () => { + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...southManifestList[0] // folder-scanner + } + ]); + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0]); + const callback = jest.fn(); + await service.testSouthItem(testData.historyQueries.list[0].id, testData.south.command, testData.south.itemCommand, callback, logger); + expect(southService.testSouthItem).toHaveBeenCalled(); + }); + + it('testSouthItem() should fail to test South connector in edit mode if south connector not found', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(null); + const callback = jest.fn(); + + await expect( + service.testSouthItem(testData.historyQueries.list[0].id, testData.south.command, testData.south.itemCommand, callback, logger) + ).rejects.toThrow(`History query ${testData.historyQueries.list[0].id} not found`); + expect(southService.testSouthItem).not.toHaveBeenCalled(); + }); + it('should create History query', () => { const historyQuery = service.runHistoryQuery(testData.historyQueries.list[0], mockBaseFolders(testData.historyQueries.list[0].id)); expect(historyQuery).toBeDefined(); expect(historyQuery['baseFolders']).toEqual(mockBaseFolders(testData.historyQueries.list[0].id)); }); - it('shold create History query with default base folders', () => { + it('should create History query with default base folders', () => { const historyQuery = service.runHistoryQuery(testData.historyQueries.list[0]); expect(historyQuery).toBeDefined(); expect(historyQuery['baseFolders']).toEqual(mockBaseFolders(`history-${testData.historyQueries.list[0].id}`)); @@ -133,4 +261,638 @@ describe('history query service', () => { ); } }); + + it('createHistoryQuery() should create a history query with items', async () => { + (northService.getInstalledNorthManifests as jest.Mock).mockReturnValueOnce([ + { + ...northManifestList[4] // file-writer + } + ]); + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...southManifestList[4] // mssql + } + ]); + + await service.createHistoryQuery(testData.historyQueries.command); + expect(historyQueryRepository.saveHistoryQuery).toHaveBeenCalledTimes(1); + expect(oIAnalyticsMessageService.createHistoryQueryMessage).toHaveBeenCalledTimes(1); + expect(historyQueryEngine.createHistoryQuery).toHaveBeenCalledTimes(1); + }); + + it('createHistoryQuery() should fail to create if manifest South not found', async () => { + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...testData.south.manifest, + id: 'another' + } + ]); + + await expect(service.createHistoryQuery(testData.historyQueries.command)).rejects.toThrow( + `South manifest ${testData.historyQueries.command.southType} does not exist` + ); + }); + + it('createHistoryQuery() should fail to create if manifest North not found', async () => { + (northService.getInstalledNorthManifests as jest.Mock).mockReturnValueOnce([ + { + ...testData.south.manifest, + id: 'another' + } + ]); + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...southManifestList[4] // mssql + } + ]); + + await expect(service.createHistoryQuery(testData.historyQueries.command)).rejects.toThrow( + `North manifest ${testData.historyQueries.command.northType} does not exist` + ); + }); + + it('should get history query data stream', () => { + service.getHistoryQueryDataStream(testData.historyQueries.list[0].id); + expect(historyQueryEngine.getHistoryQueryDataStream).toHaveBeenCalledWith(testData.historyQueries.list[0].id); + }); + + it('updateHistoryQuery() should create a history query with items', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0]); + (northService.getInstalledNorthManifests as jest.Mock).mockReturnValueOnce([ + { + ...northManifestList[4] // file-writer + } + ]); + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...southManifestList[4] // mssql + } + ]); + + await service.updateHistoryQuery(testData.historyQueries.list[0].id, testData.historyQueries.command, false); + expect(historyQueryRepository.saveHistoryQuery).toHaveBeenCalledTimes(1); + expect(oIAnalyticsMessageService.createHistoryQueryMessage).toHaveBeenCalledTimes(1); + expect(historyQueryEngine.reloadHistoryQuery).toHaveBeenCalledTimes(1); + }); + + it('updateHistoryQuery() should fail to update if history not found', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(null); + + await expect(service.updateHistoryQuery(testData.historyQueries.list[0].id, testData.historyQueries.command, false)).rejects.toThrow( + `History query ${testData.historyQueries.list[0].id} not found` + ); + }); + + it('updateHistoryQuery() should fail to update if manifest South not found', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0]); + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...testData.south.manifest, + id: 'another' + } + ]); + + await expect(service.updateHistoryQuery(testData.historyQueries.list[0].id, testData.historyQueries.command, false)).rejects.toThrow( + `South manifest not found for type ${testData.historyQueries.command.southType}` + ); + }); + + it('updateHistoryQuery() should fail to update if manifest North not found', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0]); + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...southManifestList[4] // mssql + } + ]); + (northService.getInstalledNorthManifests as jest.Mock).mockReturnValueOnce([ + { + ...testData.south.manifest, + id: 'another' + } + ]); + + await expect(service.updateHistoryQuery(testData.historyQueries.list[0].id, testData.historyQueries.command, false)).rejects.toThrow( + `North manifest not found for type ${testData.historyQueries.command.northType}` + ); + }); + + it('deleteHistoryQuery() should fail to delete if history not found', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(null); + + await expect(service.deleteHistoryQuery(testData.historyQueries.list[0].id)).rejects.toThrow( + `History query ${testData.historyQueries.list[0].id} not found` + ); + }); + + it('deleteHistoryQuery() should delete history query', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0]); + + await service.deleteHistoryQuery(testData.historyQueries.list[0].id); + expect(historyQueryEngine.deleteHistoryQuery).toHaveBeenCalledWith(testData.historyQueries.list[0]); + expect(historyQueryRepository.deleteHistoryQuery).toHaveBeenCalledWith(testData.historyQueries.list[0].id); + expect(historyQueryMetricsRepository.removeMetrics).toHaveBeenCalledWith(testData.historyQueries.list[0].id); + expect(oIAnalyticsMessageService.createHistoryQueryMessage).toHaveBeenCalledWith(testData.historyQueries.list[0]); + }); + + it('startHistoryQuery() should fail to start if history not found', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(null); + + await expect(service.startHistoryQuery(testData.historyQueries.list[0].id)).rejects.toThrow( + `History query ${testData.historyQueries.list[0].id} not found` + ); + }); + + it('startHistoryQuery() should start history query', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0]); + + await service.startHistoryQuery(testData.historyQueries.list[0].id); + expect(historyQueryRepository.updateHistoryQueryStatus).toHaveBeenCalledWith(testData.historyQueries.list[0].id, 'RUNNING'); + expect(historyQueryEngine.reloadHistoryQuery).toHaveBeenCalledWith(testData.historyQueries.list[0], false); + }); + + it('pauseHistoryQuery() should fail to pause if history not found', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(null); + + await expect(service.pauseHistoryQuery(testData.historyQueries.list[0].id)).rejects.toThrow( + `History query ${testData.historyQueries.list[0].id} not found` + ); + }); + + it('pauseHistoryQuery() should pause history query', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0]); + + await service.pauseHistoryQuery(testData.historyQueries.list[0].id); + expect(historyQueryRepository.updateHistoryQueryStatus).toHaveBeenCalledWith(testData.historyQueries.list[0].id, 'PAUSED'); + expect(historyQueryEngine.stopHistoryQuery).toHaveBeenCalledWith(testData.historyQueries.list[0].id); + }); + + it('listItems() should list history query items', async () => { + service.listItems(testData.historyQueries.list[0].id, {}); + expect(historyQueryRepository.listHistoryQueryItems).toHaveBeenCalledWith(testData.historyQueries.list[0].id, {}); + }); + + it('searchHistoryQueryItems() should list history query items', async () => { + service.searchHistoryQueryItems(testData.historyQueries.list[0].id, {}); + expect(historyQueryRepository.searchHistoryQueryItems).toHaveBeenCalledWith(testData.historyQueries.list[0].id, {}); + }); + + it('findAllItemsForHistoryQuery() should list history query items', async () => { + service.findAllItemsForHistoryQuery(testData.historyQueries.list[0].id); + expect(historyQueryRepository.findAllItemsForHistoryQuery).toHaveBeenCalledWith(testData.historyQueries.list[0].id); + }); + + it('findHistoryQueryItem() should find a history query item', async () => { + service.findHistoryQueryItem(testData.historyQueries.list[0].id, testData.historyQueries.list[0].items[0].id); + expect(historyQueryRepository.findHistoryQueryItemById).toHaveBeenCalledWith( + testData.historyQueries.list[0].id, + testData.historyQueries.list[0].items[0].id + ); + }); + + it('createHistoryQueryItem() should create an item', async () => { + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...southManifestList[0], // folder-scanner + id: testData.historyQueries.list[0].southType + } + ]); + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0]); + const itemCommand = JSON.parse(JSON.stringify(testData.south.itemCommand)); + itemCommand.settings = { + regex: '*', + preserveFiles: true, + ignoreModifiedDate: false, + minAge: 100 + }; + await service.createHistoryQueryItem(testData.historyQueries.list[0].id, itemCommand); + expect(historyQueryRepository.findHistoryQueryById).toHaveBeenCalledWith(testData.historyQueries.list[0].id); + expect(historyQueryRepository.saveHistoryQueryItem).toHaveBeenCalledTimes(1); + expect(oIAnalyticsMessageService.createHistoryQueryMessage).toHaveBeenCalledTimes(1); + expect(historyQueryEngine.reloadHistoryQuery).toHaveBeenCalledWith(testData.historyQueries.list[0], false); + }); + + it('createHistoryQueryItem() should throw an error if connector does not exist', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(null); + const itemCommand = JSON.parse(JSON.stringify(testData.south.itemCommand)); + itemCommand.settings = { + regex: '*', + preserveFiles: true, + ignoreModifiedDate: false, + minAge: 100 + }; + await expect(service.createHistoryQueryItem(testData.historyQueries.list[0].id, itemCommand)).rejects.toThrow( + `History Query ${testData.historyQueries.list[0].id} does not exist` + ); + }); + + it('createHistoryQueryItem() should throw an error if manifest is not found', async () => { + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...southManifestList[4] // mssql + } + ]); + const badSouth = JSON.parse(JSON.stringify(testData.historyQueries.list[0])); + badSouth.southType = 'bad'; + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(badSouth); + const itemCommand = JSON.parse(JSON.stringify(testData.historyQueries.itemCommand)); + itemCommand.settings = { + regex: '*', + preserveFiles: true, + ignoreModifiedDate: false, + minAge: 100 + }; + await expect(service.createHistoryQueryItem(testData.historyQueries.list[0].id, itemCommand)).rejects.toThrow( + `South manifest does not exist for type bad` + ); + }); + + it('updateHistoryQueryItem() should update an item', async () => { + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...southManifestList[4] // mssql + } + ]); + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0]); + (historyQueryRepository.findHistoryQueryItemById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0].items[0]); + const itemCommand = JSON.parse(JSON.stringify(testData.south.itemCommand)); + itemCommand.settings = { + regex: '*', + preserveFiles: true, + ignoreModifiedDate: false, + minAge: 100 + }; + await service.updateHistoryQueryItem(testData.historyQueries.list[0].id, 'itemId', itemCommand); + expect(historyQueryRepository.findHistoryQueryItemById).toHaveBeenCalledWith(testData.historyQueries.list[0].id, 'itemId'); + expect(historyQueryRepository.saveHistoryQueryItem).toHaveBeenCalledTimes(1); + expect(historyQueryEngine.reloadHistoryQuery).toHaveBeenCalledWith(testData.historyQueries.list[0], false); + }); + + it('updateHistoryQueryItem() should throw an error if item does not exist', async () => { + (historyQueryRepository.findHistoryQueryItemById as jest.Mock).mockReturnValueOnce(null); + const itemCommand = JSON.parse(JSON.stringify(testData.south.itemCommand)); + itemCommand.settings = { + regex: '*', + preserveFiles: true, + ignoreModifiedDate: false, + minAge: 100 + }; + await expect(service.updateHistoryQueryItem(testData.historyQueries.list[0].id, 'itemId', itemCommand)).rejects.toThrow( + `History query item with ID itemId does not exist` + ); + }); + + it('updateHistoryQueryItem() should throw an error if history query does not exist', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(null); + (historyQueryRepository.findHistoryQueryItemById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0].items[0]); + + const itemCommand = JSON.parse(JSON.stringify(testData.south.itemCommand)); + itemCommand.settings = { + regex: '*', + preserveFiles: true, + ignoreModifiedDate: false, + minAge: 100 + }; + await expect(service.updateHistoryQueryItem(testData.historyQueries.list[0].id, 'itemId', itemCommand)).rejects.toThrow( + `History query ${testData.historyQueries.list[0].id} does not exist` + ); + }); + + it('updateHistoryQueryItem() should throw an error if manifest is not found', async () => { + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...southManifestList[4] // mssql + } + ]); + const badSouth = JSON.parse(JSON.stringify(testData.historyQueries.list[0])); + badSouth.southType = 'bad'; + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(badSouth); + (historyQueryRepository.findHistoryQueryItemById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0].items[0]); + + const itemCommand = JSON.parse(JSON.stringify(testData.south.itemCommand)); + itemCommand.settings = { + regex: '*', + preserveFiles: true, + ignoreModifiedDate: false, + minAge: 100 + }; + await expect(service.updateHistoryQueryItem(testData.historyQueries.list[0].id, 'itemId', itemCommand)).rejects.toThrow( + `South manifest does not exist for type bad` + ); + }); + + it('deleteHistoryQueryItem() should delete an item', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0]); + (historyQueryRepository.findHistoryQueryItemById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0].items[0]); + await service.deleteHistoryQueryItem(testData.historyQueries.list[0].id, 'itemId'); + expect(historyQueryRepository.findHistoryQueryItemById).toHaveBeenCalledWith(testData.historyQueries.list[0].id, 'itemId'); + expect(historyQueryRepository.deleteHistoryQueryItem).toHaveBeenCalledWith(testData.historyQueries.list[0].items[0].id); + expect(historyQueryEngine.reloadHistoryQuery).toHaveBeenCalledWith(testData.historyQueries.list[0], false); + }); + + it('deleteHistoryQueryItem() should throw an error if item does not exist', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0]); + (historyQueryRepository.findHistoryQueryItemById as jest.Mock).mockReturnValueOnce(null); + + await expect(service.deleteHistoryQueryItem(testData.historyQueries.list[0].id, 'itemId')).rejects.toThrow( + `History query item itemId not found` + ); + }); + + it('deleteHistoryQueryItem() should throw an error if connector does not exist', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(null); + + await expect(service.deleteHistoryQueryItem(testData.historyQueries.list[0].id, 'itemId')).rejects.toThrow( + `History query ${testData.historyQueries.list[0].id} does not exist` + ); + }); + + it('deleteAllItemsForHistoryQuery() should delete all items', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0]); + await service.deleteAllItemsForHistoryQuery(testData.historyQueries.list[0].id); + expect(historyQueryRepository.deleteAllHistoryQueryItemsByHistoryQuery).toHaveBeenCalledWith(testData.historyQueries.list[0].id); + expect(historyQueryEngine.reloadHistoryQuery).toHaveBeenCalledWith(testData.historyQueries.list[0], true); + }); + + it('deleteAllItemsForHistoryQuery() should throw an error if connector does not exist', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(null); + + await expect(service.deleteAllItemsForHistoryQuery(testData.historyQueries.list[0].id)).rejects.toThrow( + `History query ${testData.historyQueries.list[0].id} not found` + ); + }); + + it('enableHistoryQueryItem() should enable an item', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0]); + (historyQueryRepository.findHistoryQueryItemById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0].items[0]); + await service.enableHistoryQueryItem(testData.historyQueries.list[0].id, 'itemId'); + expect(historyQueryRepository.findHistoryQueryItemById).toHaveBeenCalledWith(testData.historyQueries.list[0].id, 'itemId'); + expect(historyQueryRepository.enableHistoryQueryItem).toHaveBeenCalledWith(testData.historyQueries.list[0].items[0].id); + expect(historyQueryEngine.reloadHistoryQuery).toHaveBeenCalledWith(testData.historyQueries.list[0], false); + }); + + it('enableHistoryQueryItem() should throw an error if item is not found', async () => { + (historyQueryRepository.findHistoryQueryItemById as jest.Mock).mockReturnValueOnce(null); + await expect(service.enableHistoryQueryItem(testData.historyQueries.list[0].id, 'itemId')).rejects.toThrow( + 'History query item itemId not found' + ); + }); + + it('disableHistoryQueryItem() should disable an item', async () => { + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0]); + (historyQueryRepository.findHistoryQueryItemById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0].items[0]); + await service.disableHistoryQueryItem(testData.historyQueries.list[0].id, 'itemId'); + expect(historyQueryRepository.findHistoryQueryItemById).toHaveBeenCalledWith(testData.historyQueries.list[0].id, 'itemId'); + expect(historyQueryRepository.disableHistoryQueryItem).toHaveBeenCalledWith(testData.historyQueries.list[0].items[0].id); + expect(historyQueryEngine.reloadHistoryQuery).toHaveBeenCalledWith(testData.historyQueries.list[0], false); + }); + + it('disableHistoryQueryItem() should throw an error if item is not found', async () => { + (historyQueryRepository.findHistoryQueryItemById as jest.Mock).mockReturnValueOnce(null); + await expect(service.disableHistoryQueryItem(testData.historyQueries.list[0].id, 'itemId')).rejects.toThrow( + 'History query item itemId not found' + ); + }); + + it('checkCsvImport() should properly parse csv and check items', async () => { + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...southManifestList[0], // folder scanner + id: testData.historyQueries.command.southType + } + ]); + const csvData = [ + { + name: 'item1', + enabled: 'true', + settings_regex: '*', + settings_preserveFiles: 'true', + settings_ignoreModifiedDate: 'false', + settings_minAge: 100 + }, + { + name: 'item3', + enabled: 'true', + settings_regex: '*', + settings_preserveFiles: 'true', + settings_ignoreModifiedDate: 'false', + settings_minAge: 100, + settings_badItem: 100 + }, + { + name: 'item4', + enabled: 'true', + settings_regex: '*', + settings_preserveFiles: 'true', + settings_ignoreModifiedDate: 12, // bad type + settings_minAge: 100 + }, + { + name: 'item5', + enabled: 'true', + settings_regex: '*', + settings_preserveFiles: 'true', + settings_ignoreModifiedDate: 'false', + settings_minAge: 100 + } + ]; + (fs.readFile as jest.Mock).mockReturnValueOnce('file content'); + (csv.parse as jest.Mock).mockReturnValueOnce({ + meta: { delimiter: ',' }, + data: csvData + }); + (validator.validateSettings as jest.Mock).mockImplementationOnce(() => { + throw new Error(`"ignoreModifiedDate" must be a boolean`); + }); + const result = await service.checkCsvFileImport( + testData.historyQueries.command.southType, + { path: 'file/path.csv' } as multer.File, + ',', + testData.south.list[0].items + ); + expect(result).toEqual({ + items: [ + { + id: '', + name: csvData[3].name, + enabled: csvData[3].enabled.toLowerCase() === 'true', + settings: { + ignoreModifiedDate: 'false', + minAge: 100, + preserveFiles: 'true', + regex: '*' + } + } + ], + errors: [ + { + error: 'Item name "item1" already used', + item: { + id: '', + name: csvData[0].name, + enabled: csvData[0].enabled.toLowerCase() === 'true', + settings: {} + } + }, + { + error: 'Settings "badItem" not accepted in manifest', + item: { + id: '', + name: csvData[1].name, + enabled: csvData[1].enabled.toLowerCase() === 'true', + settings: {} + } + }, + { + error: '"ignoreModifiedDate" must be a boolean', + item: { + id: '', + name: csvData[2].name, + enabled: csvData[2].enabled.toLowerCase() === 'true', + settings: { + ignoreModifiedDate: 12, + minAge: 100, + preserveFiles: 'true', + regex: '*' + } + } + } + ] + }); + }); + + it('checkCsvImport() should properly parse csv and check items with array or object', async () => { + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...southManifestList[4], // mssql + id: testData.historyQueries.command.southType + } + ]); + const csvData = [ + { + name: 'item', + enabled: 'true', + settings_query: 'SELECT * FROM table', + settings_dateTimeFields: '[]', + settings_serialization: JSON.stringify({ + type: 'csv', + filename: 'filename', + delimiter: 'SEMI_COLON', + compression: true, + outputTimestampFormat: 'YYYY-MM-DD HH:mm:ss.SSS', + outputTimezone: 'Europe/Paris' + }) + } + ]; + (fs.readFile as jest.Mock).mockReturnValueOnce('file content'); + (csv.parse as jest.Mock).mockReturnValueOnce({ + meta: { delimiter: ',' }, + data: csvData + }); + const result = await service.checkCsvFileImport( + testData.historyQueries.command.southType, + { path: 'file/path.csv' } as multer.File, + ',', + testData.south.list[1].items + ); + expect(result).toEqual({ + items: [ + { + id: '', + name: csvData[0].name, + enabled: csvData[0].enabled.toLowerCase() === 'true', + settings: { + query: 'SELECT * FROM table', + dateTimeFields: [], + serialization: { + type: 'csv', + filename: 'filename', + delimiter: 'SEMI_COLON', + compression: true, + outputTimestampFormat: 'YYYY-MM-DD HH:mm:ss.SSS', + outputTimezone: 'Europe/Paris' + } + } + } + ], + errors: [] + }); + }); + + it('checkCsvContentImport() should throw error if manifest not found', async () => { + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...testData.south.manifest, + id: testData.historyQueries.command.southType + } + ]); + await expect(service.checkCsvContentImport('bad', 'fileContent', ',', testData.south.list[0].items)).rejects.toThrow( + `South manifest does not exist for type bad` + ); + }); + + it('checkCsvImport() should throw error if delimiter does not match', async () => { + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...testData.south.manifest, + id: testData.historyQueries.command.southType + } + ]); + (fs.readFile as jest.Mock).mockReturnValueOnce('file content'); + (csv.parse as jest.Mock).mockReturnValueOnce({ + meta: { delimiter: ';' }, + data: [] + }); + + await expect( + service.checkCsvFileImport( + testData.historyQueries.command.southType, + { path: 'file/path.csv' } as multer.File, + ',', + testData.south.list[0].items + ) + ).rejects.toThrow(`The entered delimiter "," does not correspond to the file delimiter ";"`); + }); + + it('importItems() should import items', async () => { + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...testData.south.manifest, + id: testData.historyQueries.command.southType + } + ]); + const itemCommand = JSON.parse(JSON.stringify(testData.historyQueries.itemCommand)); + itemCommand.id = null; + itemCommand.settings = { + regex: '*', + preserveFiles: true, + ignoreModifiedDate: false, + minAge: 100 + }; + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(testData.historyQueries.list[0]); + + await service.importItems(testData.historyQueries.list[0].id, [itemCommand]); + expect(historyQueryRepository.saveAllItems).toHaveBeenCalledTimes(1); + expect(historyQueryEngine.reloadHistoryQuery).toHaveBeenCalledWith(testData.historyQueries.list[0], false); + }); + + it('importItems() should import items', async () => { + (southService.getInstalledSouthManifests as jest.Mock).mockReturnValueOnce([ + { + ...testData.south.manifest, + id: testData.historyQueries.command.southType + } + ]); + const itemCommand = JSON.parse(JSON.stringify(testData.historyQueries.itemCommand)); + itemCommand.settings = { + regex: '*', + preserveFiles: true, + ignoreModifiedDate: false, + minAge: 100 + }; + (historyQueryRepository.findHistoryQueryById as jest.Mock).mockReturnValueOnce(null); + + await expect(service.importItems(testData.historyQueries.list[0].id, [itemCommand])).rejects.toThrow( + `History query ${testData.historyQueries.list[0].id} does not exist` + ); + }); }); diff --git a/backend/src/service/history-query.service.ts b/backend/src/service/history-query.service.ts index bb15e23551..3212b2dda2 100644 --- a/backend/src/service/history-query.service.ts +++ b/backend/src/service/history-query.service.ts @@ -163,11 +163,11 @@ export default class HistoryQueryService { ): Promise> { const southManifest = this.southService.getInstalledSouthManifests().find(southManifest => southManifest.id === command.southType); if (!southManifest) { - throw new Error('South manifest does not exist'); + throw new Error(`South manifest ${command.southType} does not exist`); } const northManifest = this.northService.getInstalledNorthManifests().find(southManifest => southManifest.id === command.northType); if (!northManifest) { - throw new Error('North manifest does not exist'); + throw new Error(`North manifest ${command.northType} does not exist`); } await this.validator.validateSettings(northManifest.settings, command.northSettings); await this.validator.validateSettings(southManifest.settings, command.southSettings); @@ -303,7 +303,7 @@ export default class HistoryQueryService { } const manifest = this.southService.getInstalledSouthManifests().find(southManifest => southManifest.id === historyQuery.southType); if (!manifest) { - throw new Error('South manifest does not exist'); + throw new Error(`South manifest does not exist for type ${historyQuery.southType}`); } await this.validator.validateSettings(manifest.items.settings, command.settings); @@ -330,15 +330,15 @@ export default class HistoryQueryService { ): Promise { const previousSettings = this.historyQueryRepository.findHistoryQueryItemById(historyQueryId, historyQueryItemId); if (!previousSettings) { - throw new Error(`History query item ${historyQueryItemId} not found`); + throw new Error(`History query item with ID ${historyQueryItemId} does not exist`); } const historyQuery = this.historyQueryRepository.findHistoryQueryById(historyQueryId); if (!historyQuery) { - throw new Error(`History Query ${historyQueryId} does not exist`); + throw new Error(`History query ${historyQueryId} does not exist`); } const manifest = this.southService.getInstalledSouthManifests().find(southManifest => southManifest.id === historyQuery.southType); if (!manifest) { - throw new Error('South manifest does not exist'); + throw new Error(`South manifest does not exist for type ${historyQuery.southType}`); } await this.validator.validateSettings(manifest.items.settings, command.settings); @@ -351,7 +351,6 @@ export default class HistoryQueryService { this.encryptionService ); this.historyQueryRepository.saveHistoryQueryItem(historyQuery.id, historyQueryItemEntity); - this.oIAnalyticsMessageService.createFullConfigMessageIfNotPending(); await this.historyQueryEngine.reloadHistoryQuery(historyQuery, false); } @@ -361,10 +360,9 @@ export default class HistoryQueryService { throw new Error(`History query ${historyQueryId} does not exist`); } const historyQueryItem = this.historyQueryRepository.findHistoryQueryItemById(historyQueryId, historyQueryItemId); - if (!historyQueryItem) throw new Error('History Query item not found'); + if (!historyQueryItem) throw new Error(`History query item ${historyQueryItemId} not found`); this.historyQueryRepository.deleteHistoryQueryItem(historyQueryItem.id); - this.oIAnalyticsMessageService.createFullConfigMessageIfNotPending(); await this.historyQueryEngine.reloadHistoryQuery(historyQuery, false); } @@ -374,7 +372,6 @@ export default class HistoryQueryService { throw new Error(`History query ${historyQueryId} not found`); } this.historyQueryRepository.deleteAllHistoryQueryItemsByHistoryQuery(historyQueryId); - this.oIAnalyticsMessageService.createFullConfigMessageIfNotPending(); await this.historyQueryEngine.reloadHistoryQuery(historyQuery, true); } @@ -385,7 +382,6 @@ export default class HistoryQueryService { } this.historyQueryRepository.enableHistoryQueryItem(historyQueryItem.id); - this.oIAnalyticsMessageService.createFullConfigMessageIfNotPending(); await this.historyQueryEngine.reloadHistoryQuery(this.historyQueryRepository.findHistoryQueryById(historyQueryId)!, false); } @@ -395,27 +391,41 @@ export default class HistoryQueryService { throw new Error(`History query item ${historyQueryItemId} not found`); } - this.historyQueryRepository.disableHistoryQueryItem(historyQueryItemId); - this.oIAnalyticsMessageService.createFullConfigMessageIfNotPending(); + this.historyQueryRepository.disableHistoryQueryItem(historyQueryItem.id); await this.historyQueryEngine.reloadHistoryQuery(this.historyQueryRepository.findHistoryQueryById(historyQueryId)!, false); } - async checkCsvImport( + async checkCsvFileImport( southType: string, file: multer.File, delimiter: string, existingItems: Array | HistoryQueryItemCommandDTO> - ) { + ): Promise<{ + items: Array>; + errors: Array<{ item: HistoryQueryItemCommandDTO; error: string }>; + }> { + const fileContent = await fs.readFile(file.path); + return await this.checkCsvContentImport(southType, fileContent.toString('utf8'), delimiter, existingItems); + } + + async checkCsvContentImport( + southType: string, + fileContent: string, + delimiter: string, + existingItems: Array | HistoryQueryItemCommandDTO> + ): Promise<{ + items: Array>; + errors: Array<{ item: HistoryQueryItemCommandDTO; error: string }>; + }> { const manifest = this.southService.getInstalledSouthManifests().find(southManifest => southManifest.id === southType); if (!manifest) { - throw new Error('South manifest not found'); + throw new Error(`South manifest does not exist for type ${southType}`); } - const fileContent = await fs.readFile(file.path); - const csvContent = csv.parse(fileContent.toString('utf8'), { header: true, delimiter }); + const csvContent = csv.parse(fileContent, { header: true, delimiter }); if (csvContent.meta.delimiter !== delimiter) { - throw new Error('The entered delimiter does not correspond to the file delimiter'); + throw new Error(`The entered delimiter "${delimiter}" does not correspond to the file delimiter "${csvContent.meta.delimiter}"`); } const validItems: Array> = []; @@ -428,7 +438,10 @@ export default class HistoryQueryService { settings: {} as I }; if (existingItems.find(existingItem => existingItem.name === item.name)) { - errors.push({ item, error: `Item name "${(data as Record).name}" already used` }); + errors.push({ + item: item, + error: `Item name "${(data as unknown as Record).name}" already used` + }); continue; } @@ -473,7 +486,7 @@ export default class HistoryQueryService { ) { const historyQuery = this.historyQueryRepository.findHistoryQueryById(historyQueryId); if (!historyQuery) { - throw new Error(`History Query ${historyQueryId} does not exist`); + throw new Error(`History query ${historyQueryId} does not exist`); } const manifest = this.southService.getInstalledSouthManifests().find(southManifest => southManifest.id === historyQuery.southType)!; const itemsToAdd: Array> = []; @@ -490,7 +503,6 @@ export default class HistoryQueryService { itemsToAdd.push(historyQueryItemEntity); } this.historyQueryRepository.saveAllItems(historyQuery.id, itemsToAdd); - this.oIAnalyticsMessageService.createFullConfigMessageIfNotPending(); await this.historyQueryEngine.reloadHistoryQuery(historyQuery, false); } @@ -623,11 +635,11 @@ const copyHistoryQueryCommandToHistoryQueryEntity = async { - const itemEntity = { id: itemCommand?.id } as HistoryQueryItemEntity; + const itemEntity = { id: itemCommand.id } as HistoryQueryItemEntity; await copyHistoryQueryItemCommandToHistoryQueryItemEntity( itemEntity, itemCommand, - historyQueryEntity.items?.find(element => element.id === itemCommand.id) || null, + currentSettings?.items.find(element => element.id === itemCommand.id) || null, historyQueryEntity.southType, encryptionService ); diff --git a/backend/src/tests/__mocks__/history-query-engine.mock.ts b/backend/src/tests/__mocks__/history-query-engine.mock.ts index 72526dc686..9bda1608c7 100644 --- a/backend/src/tests/__mocks__/history-query-engine.mock.ts +++ b/backend/src/tests/__mocks__/history-query-engine.mock.ts @@ -13,7 +13,8 @@ export default jest.fn().mockImplementation(logger => { createHistoryQuery: jest.fn(), startHistoryQuery: jest.fn(), stopHistoryQuery: jest.fn(), - getHistoryDataStream: jest.fn(), - deleteHistoryQuery: jest.fn() + getHistoryQueryDataStream: jest.fn(), + deleteHistoryQuery: jest.fn(), + reloadHistoryQuery: jest.fn() }; }); diff --git a/backend/src/tests/__mocks__/service/history-query-service.mock.ts b/backend/src/tests/__mocks__/service/history-query-service.mock.ts index 88e7e93ad5..36544d21c4 100644 --- a/backend/src/tests/__mocks__/service/history-query-service.mock.ts +++ b/backend/src/tests/__mocks__/service/history-query-service.mock.ts @@ -23,6 +23,6 @@ export default jest.fn().mockImplementation(() => ({ deleteAllItemsForHistoryQuery: jest.fn(), enableHistoryQueryItem: jest.fn(), disableHistoryQueryItem: jest.fn(), - checkCsvImport: jest.fn(), + checkCsvFileImport: jest.fn(), importItems: jest.fn() })); diff --git a/backend/src/tests/__mocks__/service/oia/oianalytics-message-service.mock.ts b/backend/src/tests/__mocks__/service/oia/oianalytics-message-service.mock.ts index 797bd2517f..a8f5636ac1 100644 --- a/backend/src/tests/__mocks__/service/oia/oianalytics-message-service.mock.ts +++ b/backend/src/tests/__mocks__/service/oia/oianalytics-message-service.mock.ts @@ -7,6 +7,7 @@ export default jest.fn().mockImplementation(() => { run: jest.fn(), stop: jest.fn(), setLogger: jest.fn(), - createFullConfigMessageIfNotPending: jest.fn() + createFullConfigMessageIfNotPending: jest.fn(), + createHistoryQueryMessage: jest.fn() }; }); diff --git a/backend/src/tests/utils/test-data.ts b/backend/src/tests/utils/test-data.ts index a55e63da33..24f1af60ea 100644 --- a/backend/src/tests/utils/test-data.ts +++ b/backend/src/tests/utils/test-data.ts @@ -338,7 +338,12 @@ const southConnectorCommand: SouthConnectorCommandDTO scanModeId: 'scanModeId1', scanModeName: null, enabled: true, - settings: {} as SouthItemSettings + settings: { + regex: '*', + minAge: 100, + preserveFiles: true, + ignoreModifiedDate: false + } }; const northTestManifest: NorthConnectorManifest = { @@ -607,10 +617,30 @@ const historyQueryCommand: HistoryQueryCommandDTO { expect(ctx.created).toHaveBeenCalledWith(toHistoryQueryDTO(testData.historyQueries.list[0], ctx.app.encryptionService)); }); + it('create() should throw bad request on create error', async () => { + ctx.request.body = testData.historyQueries.command; + + (ctx.app.historyQueryService.createHistoryQuery as jest.Mock).mockImplementationOnce(() => { + throw new Error('create error'); + }); + await historyQueryController.createHistoryQuery(ctx); + + expect(ctx.badRequest).toHaveBeenCalledWith('create error'); + }); + it('start() should enable History query', async () => { ctx.params.id = testData.historyQueries.list[0].id; @@ -71,6 +82,17 @@ describe('History query controller', () => { expect(ctx.badRequest).not.toHaveBeenCalled(); }); + it('start() should throw bad request on start error', async () => { + ctx.request.id = testData.historyQueries.list[0].id; + + (ctx.app.historyQueryService.startHistoryQuery as jest.Mock).mockImplementationOnce(() => { + throw new Error('start error'); + }); + await historyQueryController.startHistoryQuery(ctx); + + expect(ctx.badRequest).toHaveBeenCalledWith('start error'); + }); + it('pauseHistoryQuery() should pause History query', async () => { ctx.params.id = testData.historyQueries.list[0].id; @@ -81,6 +103,110 @@ describe('History query controller', () => { expect(ctx.badRequest).not.toHaveBeenCalled(); }); + it('pause() should throw bad request on pause error', async () => { + ctx.request.id = testData.historyQueries.list[0].id; + ctx.request.body = testData.south.command; + + (ctx.app.historyQueryService.pauseHistoryQuery as jest.Mock).mockImplementationOnce(() => { + throw new Error('pause error'); + }); + await historyQueryController.pauseHistoryQuery(ctx); + + expect(ctx.badRequest).toHaveBeenCalledWith('pause error'); + }); + + it('testSouthConnection() should test south connection', async () => { + ctx.request.body = testData.south.command; + ctx.params.id = testData.historyQueries.list[0].id; + + ctx.app.logger.child.mockReturnValueOnce(logger); + + await historyQueryController.testSouthConnection(ctx); + + expect(ctx.app.historyQueryService.testSouth).toHaveBeenCalledWith(testData.historyQueries.list[0].id, testData.south.command, logger); + expect(ctx.noContent).toHaveBeenCalled(); + }); + + it('testSouthConnection() should return bad request', async () => { + ctx.request.body = testData.south.command; + ctx.params.id = testData.historyQueries.list[0].id; + + ctx.app.logger.child.mockReturnValueOnce(logger); + ctx.app.historyQueryService.testSouth.mockImplementationOnce(() => { + throw new Error('test error'); + }); + + await historyQueryController.testSouthConnection(ctx); + + expect(ctx.app.historyQueryService.testSouth).toHaveBeenCalledWith(testData.historyQueries.list[0].id, testData.south.command, logger); + expect(ctx.badRequest).toHaveBeenCalledWith('test error'); + }); + + it('testHistoryQueryItem() should test south item', async () => { + ctx.request.body = { south: testData.south.command, item: testData.south.itemCommand }; + ctx.params.id = testData.historyQueries.list[0].id; + + ctx.app.logger.child.mockReturnValueOnce(logger); + + await historyQueryController.testHistoryQueryItem(ctx); + + expect(ctx.app.historyQueryService.testSouthItem).toHaveBeenCalledWith( + testData.historyQueries.list[0].id, + testData.south.command, + testData.south.itemCommand, + ctx.ok, + logger + ); + }); + + it('testHistoryQueryItem() should return bad request', async () => { + ctx.request.body = { south: testData.south.command, item: testData.south.itemCommand }; + ctx.params.id = testData.historyQueries.list[0].id; + + ctx.app.logger.child.mockReturnValueOnce(logger); + ctx.app.historyQueryService.testSouthItem.mockImplementationOnce(() => { + throw new Error('test error'); + }); + + await historyQueryController.testHistoryQueryItem(ctx); + + expect(ctx.app.historyQueryService.testSouthItem).toHaveBeenCalledWith( + testData.historyQueries.list[0].id, + testData.south.command, + testData.south.itemCommand, + ctx.ok, + logger + ); + expect(ctx.badRequest).toHaveBeenCalledWith('test error'); + }); + + it('testNorthConnection() should test north connection', async () => { + ctx.request.body = testData.north.command; + ctx.params.id = testData.historyQueries.list[0].id; + + ctx.app.logger.child.mockReturnValueOnce(logger); + + await historyQueryController.testNorthConnection(ctx); + + expect(ctx.app.historyQueryService.testNorth).toHaveBeenCalledWith(testData.historyQueries.list[0].id, testData.north.command, logger); + expect(ctx.noContent).toHaveBeenCalled(); + }); + + it('testNorthConnection() should return bad request', async () => { + ctx.request.body = testData.north.command; + ctx.params.id = testData.historyQueries.list[0].id; + + ctx.app.logger.child.mockReturnValueOnce(logger); + ctx.app.historyQueryService.testNorth.mockImplementationOnce(() => { + throw new Error('test error'); + }); + + await historyQueryController.testNorthConnection(ctx); + + expect(ctx.app.historyQueryService.testNorth).toHaveBeenCalledWith(testData.historyQueries.list[0].id, testData.north.command, logger); + expect(ctx.badRequest).toHaveBeenCalledWith('test error'); + }); + it('update() should update History Query', async () => { ctx.request.body = testData.historyQueries.command; ctx.params.id = testData.historyQueries.list[0].id; @@ -96,6 +222,17 @@ describe('History query controller', () => { expect(ctx.noContent).toHaveBeenCalled(); }); + it('update() should throw bad request on update error', async () => { + ctx.request.body = testData.historyQueries.command; + + (ctx.app.historyQueryService.updateHistoryQuery as jest.Mock).mockImplementationOnce(() => { + throw new Error('update error'); + }); + await historyQueryController.updateHistoryQuery(ctx); + + expect(ctx.badRequest).toHaveBeenCalledWith('update error'); + }); + it('deleteHistoryQuery() should delete history query', async () => { ctx.params.id = testData.historyQueries.list[0].id; @@ -105,7 +242,18 @@ describe('History query controller', () => { expect(ctx.noContent).toHaveBeenCalled(); }); - it('searchSouthItems() should return South items', async () => { + it('delete() should throw bad request on delete error', async () => { + ctx.request.body = testData.historyQueries.command; + + (ctx.app.historyQueryService.deleteHistoryQuery as jest.Mock).mockImplementationOnce(() => { + throw new Error('delete error'); + }); + await historyQueryController.deleteHistoryQuery(ctx); + + expect(ctx.badRequest).toHaveBeenCalledWith('delete error'); + }); + + it('searchHistoryQueryItems() should return South items', async () => { ctx.params.historyQueryId = testData.historyQueries.list[0].id; ctx.query = { page: 1, @@ -141,7 +289,7 @@ describe('History query controller', () => { }); }); - it('searchSouthItems() should return South items with default search params', async () => { + it('searchHistoryQueryItems() should return South items with default search params', async () => { ctx.params.historyQueryId = testData.historyQueries.list[0].id; ctx.query = {}; const searchParams = { @@ -173,6 +321,15 @@ describe('History query controller', () => { }); }); + it('searchHistoryQueryItems() should return not found if history query is not found', async () => { + ctx.params.historyQueryId = testData.historyQueries.list[0].id; + ctx.app.historyQueryService.findById.mockReturnValueOnce(null); + + await historyQueryController.searchHistoryQueryItems(ctx); + + expect(ctx.notFound).toHaveBeenCalledTimes(1); + }); + it('getHistoryItem() should return item', async () => { ctx.params.historyQueryId = testData.historyQueries.list[0].id; ctx.params.id = testData.historyQueries.list[0].items[0].id; @@ -188,6 +345,15 @@ describe('History query controller', () => { expect(ctx.ok).toHaveBeenCalledWith(testData.historyQueries.list[0].items[0]); }); + it('getHistoryItem() should return not found if history query is not found', async () => { + ctx.params.historyQueryId = testData.historyQueries.list[0].id; + ctx.app.historyQueryService.findById.mockReturnValueOnce(null); + + await historyQueryController.getHistoryQueryItem(ctx); + + expect(ctx.notFound).toHaveBeenCalledTimes(1); + }); + it('getHistoryItem() should return not found when South item not found', async () => { ctx.params.historyQueryId = testData.historyQueries.list[0].id; ctx.params.id = testData.historyQueries.list[0].items[0].id; @@ -223,6 +389,28 @@ describe('History query controller', () => { ); }); + it('createHistoryQueryItem() should return not found if history query is not found', async () => { + ctx.params.historyQueryId = testData.historyQueries.list[0].id; + ctx.request.body = testData.historyQueries.itemCommand; + ctx.app.historyQueryService.findById.mockReturnValueOnce(null); + await historyQueryController.createHistoryQueryItem(ctx); + + expect(ctx.notFound).toHaveBeenCalledTimes(1); + }); + + it('createHistoryQueryItem() should return bad request on item creation error', async () => { + ctx.params.historyQueryId = testData.historyQueries.list[0].id; + ctx.request.body = testData.historyQueries.itemCommand; + ctx.app.historyQueryService.findById.mockReturnValueOnce(testData.historyQueries.list[0]); + + ctx.app.historyQueryService.createHistoryQueryItem.mockImplementationOnce(() => { + throw new Error('create error'); + }); + await historyQueryController.createHistoryQueryItem(ctx); + + expect(ctx.badRequest).toHaveBeenCalledWith('create error'); + }); + it('updateHistoryQueryItem() should update item', async () => { ctx.params.historyQueryId = testData.historyQueries.list[0].id; ctx.params.id = testData.historyQueries.list[0].items[0].id; @@ -238,6 +426,19 @@ describe('History query controller', () => { expect(ctx.noContent).toHaveBeenCalled(); }); + it('updateHistoryQueryItem() should return bad request on item update error', async () => { + ctx.params.historyQueryId = testData.historyQueries.list[0].id; + ctx.params.id = testData.historyQueries.list[0].items[0].id; + ctx.request.body = testData.historyQueries.itemCommand; + + ctx.app.historyQueryService.updateHistoryQueryItem.mockImplementationOnce(() => { + throw new Error('update error'); + }); + await historyQueryController.updateHistoryQueryItem(ctx); + + expect(ctx.badRequest).toHaveBeenCalledWith('update error'); + }); + it('deleteHistoryQueryItem() should delete item', async () => { ctx.params.historyQueryId = testData.historyQueries.list[0].id; ctx.params.id = testData.historyQueries.list[0].items[0].id; @@ -251,6 +452,18 @@ describe('History query controller', () => { expect(ctx.noContent).toHaveBeenCalled(); }); + it('deleteHistoryQueryItem() should return bad request on item delete error', async () => { + ctx.params.historyQueryId = testData.historyQueries.list[0].id; + ctx.params.id = testData.historyQueries.list[0].items[0].id; + + ctx.app.historyQueryService.deleteHistoryQueryItem.mockImplementationOnce(() => { + throw new Error('delete error'); + }); + await historyQueryController.deleteHistoryQueryItem(ctx); + + expect(ctx.badRequest).toHaveBeenCalledWith('delete error'); + }); + it('enableHistoryQueryItem() should enable History item', async () => { ctx.params.historyQueryId = testData.historyQueries.list[0].id; ctx.params.id = testData.historyQueries.list[0].items[0].id; @@ -264,6 +477,18 @@ describe('History query controller', () => { expect(ctx.noContent).toHaveBeenCalled(); }); + it('enableHistoryQueryItem() should return bad request on item enable error', async () => { + ctx.params.historyQueryId = testData.historyQueries.list[0].id; + ctx.params.id = testData.historyQueries.list[0].items[0].id; + + ctx.app.historyQueryService.enableHistoryQueryItem.mockImplementationOnce(() => { + throw new Error('enable error'); + }); + await historyQueryController.enableHistoryQueryItem(ctx); + + expect(ctx.badRequest).toHaveBeenCalledWith('enable error'); + }); + it('disableHistoryQueryItem() should disable History item', async () => { ctx.params.historyQueryId = testData.historyQueries.list[0].id; ctx.params.id = testData.historyQueries.list[0].items[0].id; @@ -277,6 +502,18 @@ describe('History query controller', () => { expect(ctx.noContent).toHaveBeenCalled(); }); + it('disableHistoryQueryItem() should return bad request on item disable error', async () => { + ctx.params.historyQueryId = testData.historyQueries.list[0].id; + ctx.params.id = testData.historyQueries.list[0].items[0].id; + + ctx.app.historyQueryService.disableHistoryQueryItem.mockImplementationOnce(() => { + throw new Error('disable error'); + }); + await historyQueryController.disableHistoryQueryItem(ctx); + + expect(ctx.badRequest).toHaveBeenCalledWith('disable error'); + }); + it('deleteAllItems() should delete all South items', async () => { ctx.params.historyQueryId = testData.historyQueries.list[0].id; @@ -286,7 +523,19 @@ describe('History query controller', () => { expect(ctx.noContent).toHaveBeenCalled(); }); - it('historySouthItemsToCsv() should download a csv file', async () => { + it('deleteAllItems() should return bad request on delete all item error', async () => { + ctx.params.historyQueryId = testData.historyQueries.list[0].id; + ctx.params.id = testData.historyQueries.list[0].items[0].id; + + ctx.app.historyQueryService.deleteAllItemsForHistoryQuery.mockImplementationOnce(() => { + throw new Error('delete all error'); + }); + await historyQueryController.deleteAllItems(ctx); + + expect(ctx.badRequest).toHaveBeenCalledWith('delete all error'); + }); + + it('historyQueryItemsToCsv() should download a csv file', async () => { ctx.params.southType = testData.historyQueries.list[0].southType; ctx.request.body = { items: testData.historyQueries.list[0].items, @@ -303,6 +552,19 @@ describe('History query controller', () => { expect(ctx.body).toEqual('csv content'); }); + it('historyQueryItemsToCsv() should throw not found if manifest not found', async () => { + ctx.params.southType = 'bad type'; + ctx.request.body = { + items: testData.historyQueries.list[0].items, + delimiter: ';' + }; + ctx.app.southService.getInstalledSouthManifests.mockReturnValueOnce([]); + + await historyQueryController.historyQueryItemsToCsv(ctx); + + expect(ctx.throw).toHaveBeenCalledWith(404, 'South manifest not found'); + }); + it('exportSouthItems() should download a csv file', async () => { ctx.params.historyQueryId = testData.historyQueries.list[0].id; ctx.app.historyQueryService.findById.mockReturnValueOnce(testData.historyQueries.list[0]); @@ -315,19 +577,29 @@ describe('History query controller', () => { expect(ctx.body).toEqual('csv content'); }); + it('exportSouthItems() should return not found if history query not found', async () => { + ctx.params.historyQueryId = testData.historyQueries.list[0].id; + ctx.app.historyQueryService.findById.mockReturnValueOnce(null); + ctx.request.body = { delimiter: ';' }; + + await historyQueryController.exportSouthItems(ctx); + + expect(ctx.notFound).toHaveBeenCalledWith(); + }); + it('checkImportSouthItems() should check import of items in a csv file with new history', async () => { ctx.params.historyQueryId = testData.historyQueries.list[0].id; ctx.params.southType = testData.historyQueries.list[0].southType; ctx.request.body = { delimiter: ',', currentItems: '[]' }; ctx.request.file = { path: 'myFile.csv', mimetype: 'text/csv' }; - ctx.app.historyQueryService.checkCsvImport.mockReturnValueOnce({ items: testData.historyQueries.list[0].items, errors: [] }); + ctx.app.historyQueryService.checkCsvFileImport.mockReturnValueOnce({ items: testData.historyQueries.list[0].items, errors: [] }); await historyQueryController.checkImportSouthItems(ctx); expect(ctx.badRequest).not.toHaveBeenCalled(); expect(ctx.throw).not.toHaveBeenCalled(); - expect(ctx.app.historyQueryService.checkCsvImport).toHaveBeenCalledWith( + expect(ctx.app.historyQueryService.checkCsvFileImport).toHaveBeenCalledWith( testData.historyQueries.list[0].southType, ctx.request.file, ctx.request.body.delimiter, @@ -336,6 +608,20 @@ describe('History query controller', () => { expect(ctx.ok).toHaveBeenCalledWith({ items: testData.historyQueries.list[0].items, errors: [] }); }); + it('checkImportSouthItems() should return bad request if check csv import fails', async () => { + ctx.params.historyQueryId = testData.historyQueries.list[0].id; + ctx.params.southType = testData.historyQueries.list[0].southType; + ctx.request.body = { delimiter: ',', currentItems: '[]' }; + ctx.request.file = { path: 'myFile.csv', mimetype: 'text/csv' }; + ctx.app.historyQueryService.checkCsvFileImport.mockImplementationOnce(() => { + throw new Error('check import error'); + }); + + await historyQueryController.checkImportSouthItems(ctx); + + expect(ctx.badRequest).toHaveBeenCalledWith('check import error'); + }); + it('importSouthItems() should import items', async () => { ctx.params.historyQueryId = testData.historyQueries.list[0].id; ctx.request.body = { items: testData.historyQueries.list[0].items }; @@ -347,6 +633,21 @@ describe('History query controller', () => { ); }); + it('importSouthItems() should import items', async () => { + ctx.params.historyQueryId = testData.historyQueries.list[0].id; + ctx.request.body = { items: testData.historyQueries.list[0].items }; + ctx.app.historyQueryService.importItems.mockImplementationOnce(() => { + throw new Error('import items error'); + }); + + await historyQueryController.importSouthItems(ctx); + expect(ctx.app.historyQueryService.importItems).toHaveBeenCalledWith( + testData.historyQueries.list[0].id, + testData.historyQueries.list[0].items + ); + expect(ctx.badRequest).toHaveBeenCalledWith('import items error'); + }); + it('testSouthConnection() should test South connector settings', async () => { ctx.params.id = testData.historyQueries.list[0].id; ctx.request.body = testData.south.command; diff --git a/backend/src/web-server/controllers/history-query.controller.ts b/backend/src/web-server/controllers/history-query.controller.ts index 97c9f412e2..96fc58d1fd 100644 --- a/backend/src/web-server/controllers/history-query.controller.ts +++ b/backend/src/web-server/controllers/history-query.controller.ts @@ -291,7 +291,7 @@ export default class HistoryQueryController extends AbstractController { ): Promise { try { return ctx.ok( - await ctx.app.historyQueryService.checkCsvImport( + await ctx.app.historyQueryService.checkCsvFileImport( ctx.params.southType, ctx.request.file, ctx.request.body!.delimiter,