diff --git a/src/mocks/handlers.js b/src/mocks/handlers.js index 829ef5dc8a47c4b9f0a42bfd810faa383c070bff..d0237fd453ea81093cc4943a2ed7ddec099ff0a2 100644 --- a/src/mocks/handlers.js +++ b/src/mocks/handlers.js @@ -10,6 +10,8 @@ import MockUsers from './mock_users.json'; import MockSimulations from './mock_simulations.json'; import MockUserGroups from './mock_user-groups.json'; import MockGDPR from './mock_gdpr.json'; +import MockModels from './mock_models.json'; +import MockCustomModels from './mock_custom_models.json'; import ImageAI from '../assets/images/Artificial_Intelligence_2.jpg'; @@ -58,6 +60,27 @@ export const handlers = [ }), rest.post(`${config.api.proxy.url}${endpoints.proxy.identity.url}${endpoints.proxy.identity.gdpr.url}`, (req, res, ctx) => { - return res(ctx.json({'status':'success'})); - }) + return res(ctx.json({ 'status': 'success' })); + }), + rest.get(`${config.api.proxy.url}${endpoints.proxy.models.url}/:modelType`, + (req, res, ctx) => { + return res(ctx.json(MockModels[0])); + }), + rest.post(`${config.api.proxy.url}${endpoints.proxy.models.url}/:modelType/:modelName`, + (req, res, ctx) => { + return res(ctx.json(MockCustomModels[2])); + }), + rest.get(`${config.api.proxy.url}${endpoints.proxy.storage.allCustomModels.url}/:modelType`, + (req, res, ctx) => { + return res(ctx.json(MockCustomModels[0])); + }), + rest.get(`${config.api.proxy.url}${endpoints.proxy.storage.userModels.url}/:modelType`, + (req, res, ctx) => { + return res(ctx.json(MockCustomModels[0])); + }), + rest.delete(`${config.api.proxy.url}${endpoints.proxy.storage.userModels.url}/:modelType/:modelName`, + (req, res, ctx) => { + return res(ctx.json(MockCustomModels[1])); + }), + ]; \ No newline at end of file diff --git a/src/mocks/mock_custom_models.json b/src/mocks/mock_custom_models.json new file mode 100644 index 0000000000000000000000000000000000000000..3b312f522b3653e213da73a590223b99935ae14f --- /dev/null +++ b/src/mocks/mock_custom_models.json @@ -0,0 +1,20 @@ +[ + { + "name": "custom_hbp_clearpath_robotics_husky_a200", + "path": "robots/husky_model.zip", + "ownerName": "nrpuser", + "type": "robots", + "fileName": "husky_model.zip" + }, + { + "name": "deleted_hbp_clearpath_robotics_husky_a200", + "ownerName": "nrpuser", + "type": "robots" + }, + { + "name": "created_hbp_clearpath_robotics_husky_a200", + "ownerName": "nrpuser", + "type": "robots" + } + +] \ No newline at end of file diff --git a/src/mocks/mock_models.json b/src/mocks/mock_models.json new file mode 100644 index 0000000000000000000000000000000000000000..2f2905b11a72d9128392831dcb72473bb215d1f4 --- /dev/null +++ b/src/mocks/mock_models.json @@ -0,0 +1,9 @@ +[ + { + "name": "hbp_clearpath_robotics_husky_a200", + "path": "robots/husky_model.zip", + "ownerName": "nrpuser", + "type": "robots", + "fileName": "husky_model.zip" + } +] \ No newline at end of file diff --git a/src/services/__tests__/models-storage-service.test.js b/src/services/__tests__/models-storage-service.test.js new file mode 100644 index 0000000000000000000000000000000000000000..94603b8af20db8b9813028d0c8209fd8aaa0162c --- /dev/null +++ b/src/services/__tests__/models-storage-service.test.js @@ -0,0 +1,70 @@ +/** + * @jest-environment jsdom +*/ +import '@testing-library/jest-dom'; +import 'jest-fetch-mock'; + +import ModelsStorageService from '../models/models-storage-service'; + +test('makes sure that invoking the constructor fails with the right message', () => { + expect(() => { + new ModelsStorageService(); + }).toThrow(Error); + expect(() => { + new ModelsStorageService(); + }).toThrowError(Error('Use ModelsStorageService.instance')); +}); + +test('the service instance always refers to the same object', () => { + const instance1 = ModelsStorageService.instance; + const instance2 = ModelsStorageService.instance; + expect(instance1).toBe(instance2); +}); + + +test('getTemplateModels function', async () => { + + let modelsService = ModelsStorageService.instance; + + // fetch template robots + let response = await modelsService.getTemplateModels(true,'robots',false); + expect(response.name).toBe('hbp_clearpath_robotics_husky_a200'); + expect(response.ownerName).toBe('nrpuser'); + expect(response.type).toBe('robots'); + + // fetch custom robots + response = await modelsService.getTemplateModels(true,'robots',true); + expect(response.name).toBe('custom_hbp_clearpath_robotics_husky_a200'); + expect(response.ownerName).toBe('nrpuser'); + expect(response.type).toBe('robots'); +}); + +test('getCustomModelsByUser function', async () => { + + let modelsService = ModelsStorageService.instance; + + // fetch template robots + let response = await modelsService.getCustomModelsByUser('robots'); + expect(response.name).toBe('custom_hbp_clearpath_robotics_husky_a200'); + expect(response.ownerName).toBe('nrpuser'); + expect(response.type).toBe('robots'); + +}); + +test('verifyModelType function', async () => { + + let modelsService = ModelsStorageService.instance; + const expectedErrorPart = 'Error Type 400: Bad Request : The model type notRobots'; + // fetch template robots + expect(()=>modelsService.verifyModelType('notRobots')).toThrowError(expectedErrorPart); + +}); + +test('setCustomModel function', async () => { + let modelsService = ModelsStorageService.instance; + + let response = await modelsService.setCustomModel('robots','husky','fakeContent'); + expect(response.name).toBe('created_hbp_clearpath_robotics_husky_a200'); + expect(response.ownerName).toBe('nrpuser'); + expect(response.type).toBe('robots'); +}); diff --git a/src/services/models/models-storage-service.js b/src/services/models/models-storage-service.js new file mode 100644 index 0000000000000000000000000000000000000000..342de10d94e3f089cdd8a49969fb5b4c655d7928 --- /dev/null +++ b/src/services/models/models-storage-service.js @@ -0,0 +1,146 @@ +import { HttpService } from '../http-service.js'; + +import endpoints from '../proxy/data/endpoints.json'; +import config from '../../config.json'; +import ErrorHandlerService from '../error-handler-service'; + +const storageModelsURL = `${config.api.proxy.url}${endpoints.proxy.models.url}`; +const allCustomModelsURL = `${config.api.proxy.url}${endpoints.proxy.storage.allCustomModels.url}`; +const userModelsURL = `${config.api.proxy.url}${endpoints.proxy.storage.userModels.url}`; + +let _instance = null; +const SINGLETON_ENFORCER = Symbol(); +const availableModels = ['robots', 'brains', 'environments']; +/** + * Service that manages the fetching and setting of custom and template + * models from the proxy. + */ +class ModelsStorageService extends HttpService { + constructor(enforcer) { + super(); + if (enforcer !== SINGLETON_ENFORCER) { + throw new Error('Use ' + this.constructor.name + '.instance'); + } + } + + static get instance() { + if (_instance == null) { + _instance = new ModelsStorageService(SINGLETON_ENFORCER); + } + + return _instance; + } + + /** + * Retrieves the list of template or custom models from the proxy and stores + * them in the models class property. If the models are already + * there it just returns them, else does an HTTP request. + * + * @param {boolean} forceUpdate forces an update of the list + * @param {string} modelType one of the types + * ['robots', 'brains', 'environments'] + * @param {boolean} allCustomModels if true fetch custom(user) models intead of templates + * @return models - the list of template models + */ + async getTemplateModels(forceUpdate = false, modelType, allCustomModels = false) { + if (!this.models || forceUpdate) { + try { + this.verifyModelType(modelType); + } + catch (error) { + ErrorHandlerService.instance.dataError(error); + } + + try { + const modelsWithTypeURL = allCustomModels ? + `${allCustomModelsURL}/${modelType}` : + `${storageModelsURL}/${modelType}`; + this.models = await (await this.httpRequestGET(modelsWithTypeURL)).json(); + } + catch (error) { + ErrorHandlerService.instance.networkError(error); + } + + } + + return this.models; + } + + /** + * Retrieves the list of custom models per user from the storage + * + * @param {string} modelType one of the types + * ['robots', 'brains', 'environments'] + * @return models - the list of custom user models + */ + async getCustomModelsByUser(modelType) { + try { + this.verifyModelType(modelType); + const customModelsURL = `${userModelsURL}/${modelType}`; + return (await this.httpRequestGET(customModelsURL)).json(); + } + catch (error) { + ErrorHandlerService.instance.networkError(error); + } + } + + /** + * Helper function that checks whether a specific type + * of model is in the list of available models. + * + * @param {string} modelType one of the types + * ['robots', 'brains', 'environments'] + */ + verifyModelType(modelType) { + if (!availableModels.includes(modelType)) { + throw new Error( + `Error Type 400: Bad Request : The model type ${modelType} + type that was requested is not one of brains, robots, environments.`); + } + } + + /** + * Deletes a custom model from the storage + * + * @param {string} modelType one of the types + * ['robots', 'brains', 'environments'] + * @param {string} modelName the name of the model to delete + * @return the response of the request + */ + + async deleteCustomModel(modelType, modelName) { + try { + this.verifyModelType(modelType); + const deleteCustomModelURL = `${userModelsURL}/${modelType}/${modelName}`; + return (await this.httpRequestDELETE(deleteCustomModelURL)).json(); + } + catch (error) { + ErrorHandlerService.instance.dataError(error); + } + } + + /** + * Sets a custom model to the storage + * + * @param {string} modelType one of the types + * ['robots', 'brains', 'environments'] + * @param {string} modelName the name of the model to delete + * @param fileContent the data of the model to upload + * @return the response of the request + */ + async setCustomModel(modelType, modelName, fileContent) { + + try { + this.verifyModelType(modelType); + const setCustomModelURL = `${storageModelsURL}/${modelType}/${modelName}`; + console.log(setCustomModelURL) + return (await this.httpRequestPOST(setCustomModelURL, fileContent)).json(); + } + catch (error) { + ErrorHandlerService.instance.networkError(error); + } + } +} + + +export default ModelsStorageService; diff --git a/src/services/proxy/data/endpoints.json b/src/services/proxy/data/endpoints.json index aa5c143975c1d800ae711fe54b13a30784c8122c..a2c1acd09cb0a2cd4a9ad8429d89db43003fe87c 100644 --- a/src/services/proxy/data/endpoints.json +++ b/src/services/proxy/data/endpoints.json @@ -9,6 +9,9 @@ "experiments": { "url": "/experiments" }, + "models": { + "url": "/models" + }, "identity": { "url": "/identity", "me": { @@ -37,6 +40,12 @@ }, "scanStorage": { "url": "/storage/scanStorage" + }, + "allCustomModels": { + "url": "/storage/models/all" + }, + "userModels": { + "url": "/storage/models/user" } } }