diff --git a/package-lock.json b/package-lock.json index 921140b9451369c9c08daaa1d422c693058a1146..775807cea0d15cd8c7f85a9a3dea51db568d20b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4398,9 +4398,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001237", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001237.tgz", - "integrity": "sha512-pDHgRndit6p1NR2GhzMbQ6CkRrp4VKuSsqbcLeOQppYPKOYkKT/6ZvZDvKJUqcmtyWIAHuZq3SVS2vc1egCZzw==" + "version": "1.0.30001242", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001242.tgz", + "integrity": "sha512-KvNuZ/duufelMB3w2xtf9gEWCSxJwUgoxOx5b6ScLXC4kPc9xsczUVCPrQU26j5kOsHM4pSUL54tAZt5THQKug==" }, "capture-exit": { "version": "2.0.0", diff --git a/package.json b/package.json index 45da5415fb864aa887bee48076a1964c8e6be4ee..967f68baa880658fbf7c17e924062c6064d9232f 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ "@material-ui/lab": "4.0.0-alpha.57", "bootstrap": "4.5", "flexlayout-react": "0.5.5", - "jszip": "3.2.0", "jquery": "3.6.0", + "jszip": "3.2.0", "react": "^17.0.1", "react-bootstrap": "1.4.0", "react-dom": "^17.0.1", diff --git a/src/mocks/mock_dialog.json b/src/mocks/mock_dialog.json new file mode 100644 index 0000000000000000000000000000000000000000..da3492dddfba4d9f6336ac42b9ed24ad2850572f --- /dev/null +++ b/src/mocks/mock_dialog.json @@ -0,0 +1,4 @@ +{ + "type" :"Network Error", + "message" : "The experiment is loading" +} \ No newline at end of file diff --git a/src/mocks/mock_scan_storage_response.json b/src/mocks/mock_scan_storage_response.json new file mode 100644 index 0000000000000000000000000000000000000000..afd252ca68505ea50bfbfee034c3c318579ef324 --- /dev/null +++ b/src/mocks/mock_scan_storage_response.json @@ -0,0 +1,4 @@ +{ + "deletedFolders":[0,1], + "addedFolders": [2] +} \ No newline at end of file diff --git a/src/mocks/mock_zip_responses.json b/src/mocks/mock_zip_responses.json new file mode 100644 index 0000000000000000000000000000000000000000..a255a0985dcef21706bbc399693f53d7006be465 --- /dev/null +++ b/src/mocks/mock_zip_responses.json @@ -0,0 +1,11 @@ +[ + { + "zipBaseFolderName": "0", + "destFolderName": "1" + + }, + { + "zipBaseFolderName": "0", + "destFolderName": "2" + } +] \ No newline at end of file diff --git a/src/services/__tests__/dialog-service.test.js b/src/services/__tests__/dialog-service.test.js new file mode 100644 index 0000000000000000000000000000000000000000..5f384e40fc433245f4c830d07aff878a56bdd150 --- /dev/null +++ b/src/services/__tests__/dialog-service.test.js @@ -0,0 +1,125 @@ +/** + * @jest-environment jsdom +*/ +import '@testing-library/jest-dom'; +import 'jest-fetch-mock'; + +import DialogService from '../dialog-service'; + +import MockDialog from '../../mocks/mock_dialog.json'; + +test('makes sure that invoking the constructor fails with the right message', () => { + expect(() => { + new DialogService(); + }).toThrow(Error); + expect(() => { + new DialogService(); + }).toThrowError(Error('Use DialogService.instance')); +}); + +test('the experiments service instance always refers to the same object', () => { + const instance1 = DialogService.instance; + const instance2 = DialogService.instance; + expect(instance1).toBe(instance2); +}); + +test('should emit an event on network error', () => { + jest.spyOn(DialogService.instance, 'networkError').mockImplementation(() => { + return Promise.resolve(); + }); + let NetworkError = MockDialog; + + let confirmNetworkError = (startingNetwork) => { + expect(startingNetwork).toEqual(NetworkError); + }; + DialogService.instance.addListener( + DialogService.EVENTS.Error, + confirmNetworkError + ); + DialogService.instance.networkError(NetworkError); + DialogService.instance.removeListener( + DialogService.EVENTS.Error, + confirmNetworkError + ); +}); + +test('should emit an event on data error', () => { + jest.spyOn(DialogService.instance, 'dataError').mockImplementation(() => { + return Promise.resolve(); + }); + let DataError = MockDialog; + + let confirmDataError = (startingData) => { + expect(startingData).toEqual(DataError); + }; + DialogService.instance.addListener( + DialogService.EVENTS.ERROR, + confirmDataError + ); + DialogService.instance.dataError(DataError); + DialogService.instance.removeListener( + DialogService.EVENTS.ERROR, + confirmDataError + ); +}); + +test('should emit an event on simulation error', () => { + jest.spyOn(DialogService.instance, 'simulationError').mockImplementation(() => { + return Promise.resolve(); + }); + let SimulationError = MockDialog; + + let confirmSimulationError = (startingSimulation) => { + expect(startingSimulation).toEqual(SimulationError); + }; + DialogService.instance.addListener( + DialogService.EVENTS.ERROR, + confirmSimulationError + ); + DialogService.instance.dataError(SimulationError); + DialogService.instance.removeListener( + DialogService.EVENTS.ERROR, + confirmSimulationError + ); +}); + +test('should emit an event on progress notification', () => { + jest.spyOn(DialogService.instance, 'progressNotification').mockImplementation(() => { + return Promise.resolve(); + }); + let ProgressNotification = MockDialog; + + let confirmProgressNotification = (startingProgress) => { + expect(startingProgress).toEqual(ProgressNotification); + }; + DialogService.instance.addListener( + DialogService.EVENTS.ERROR, + confirmProgressNotification + ); + DialogService.instance.dataError(ProgressNotification); + DialogService.instance.removeListener( + DialogService.EVENTS.ERROR, + confirmProgressNotification + ); +}); + +test('should emit an event on warning notification', () => { + jest.spyOn(DialogService.instance, 'warningNotification').mockImplementation(() => { + return Promise.resolve(); + }); + let WarningNotification = MockDialog; + + let confirmWarningNotification = (startingWarning) => { + expect(startingWarning).toEqual(WarningNotification); + }; + + DialogService.instance.addListener( + DialogService.EVENTS.ERROR, + confirmWarningNotification + ); + DialogService.instance.dataError(WarningNotification); + DialogService.instance.removeListener( + DialogService.EVENTS.ERROR, + confirmWarningNotification + ); +}); diff --git a/src/services/experiments/files/__tests__/import-experiment-service.test.js b/src/services/experiments/files/__tests__/import-experiment-service.test.js new file mode 100644 index 0000000000000000000000000000000000000000..e48cefb9b8a5810a223767cb932222f0d0c69757 --- /dev/null +++ b/src/services/experiments/files/__tests__/import-experiment-service.test.js @@ -0,0 +1,31 @@ +/** + * @jest-environment jsdom +*/ +import '@testing-library/jest-dom'; +import 'jest-fetch-mock'; + +import ImportExperimentService from '../import-experiment-service'; + +import MockScanStorageResponse from '../../../../mocks/mock_scan_storage_response.json'; +import MockZipResponses from '../../../../mocks/mock_zip_responses.json'; + +test('makes sure that invoking the constructor fails with the right message', () => { + expect(() => { + new ImportExperimentService(); + }).toThrow(Error); + expect(() => { + new ImportExperimentService(); + }).toThrowError(Error('Use ImportExperimentService.instance')); +}); + +test('makes sure zip responses are encapsulated in an object', async () => { + let importZipResponses = {zipBaseFolderName:['0', '0'], destFolderName:['1', '2'], numberOfZips: 2}; + expect(await ImportExperimentService.instance.getImportZipResponses( + MockZipResponses.map((response) => new Response(JSON.stringify(response))))).toStrictEqual(importZipResponses); +}); + +test('makes sure storage response is prepared', async () => { + let scanStorageResponse = {deletedFoldersNumber:2, addedFoldersNumber:1, deletedFolders:'0, 1', addedFolders:'2' }; + expect(await ImportExperimentService.instance.getScanStorageResponse( + new Response(JSON.stringify(MockScanStorageResponse)))).toStrictEqual(scanStorageResponse); +}); \ No newline at end of file diff --git a/src/services/experiments/files/import-experiment-service.js b/src/services/experiments/files/import-experiment-service.js index 5ae1b74db8e043de07d94bd2cad534237542ca8a..d7307b9f5a73017969c626e045f03d30b0de4638 100644 --- a/src/services/experiments/files/import-experiment-service.js +++ b/src/services/experiments/files/import-experiment-service.js @@ -7,8 +7,19 @@ import DialogService from '../../dialog-service.js'; const importExperimentURL = `${config.api.proxy.url}${endpoints.proxy.storage.importExperiment.url}`; const scanStorageURL = `${config.api.proxy.url}${endpoints.proxy.storage.scanStorage.url}`; +/** + * The Import Experiment Service performs the requests (Extract), + * processes data such as zip or folder (Transform), + * and passes them to the Import Experiment Component (Load). + * Errors are handled by communicating with the Error Handler Service. + */ + let _instance = null; const SINGLETON_ENFORCER = Symbol(); + +/** + * Non-default options (content type) for the POST request + */ const options = { mode: 'cors', // no-cors, *cors, same-origin cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached @@ -42,13 +53,13 @@ export default class ImportExperimentService extends HttpService { return _instance; } - getImportZipResponses(responses) { + async getImportZipResponses(responses) { let importZipResponses = { zipBaseFolderName: [], destFolderName: [] }; importZipResponses.numberOfZips = responses.length; - responses.forEach(async response =>{ + await responses.forEach(async response =>{ response = await response.json(); importZipResponses['zipBaseFolderName'].push(response['zipBaseFolderName']); importZipResponses['destFolderName'].push(response['destFolderName']); @@ -129,7 +140,7 @@ export default class ImportExperimentService extends HttpService { }); } - readZippedExperimentExperiment(event) { + readZippedExperiment(event) { let files = event.target.files; let zipFiles = []; Array.from(files).forEach(file => { @@ -146,7 +157,7 @@ export default class ImportExperimentService extends HttpService { } importZippedExperiment(event) { - let promises = this.readZippedExperimentExperiment(event) + let promises = this.readZippedExperiment(event) .then(zipContents => zipContents.map(zipContent => this.httpRequestPOST(importExperimentURL, zipContent, options)