diff --git a/Control/lib/control-core/GrpcProxy.js b/Control/lib/control-core/GrpcProxy.js index 2c34c7d38..82e8830b0 100644 --- a/Control/lib/control-core/GrpcProxy.js +++ b/Control/lib/control-core/GrpcProxy.js @@ -16,7 +16,7 @@ const protoLoader = require('@grpc/proto-loader'); const grpcLibrary = require('@grpc/grpc-js'); const path = require('path'); -const {grpcErrorToNativeError} = require('./../errors/grpcErrorToNativeError.js'); +const {grpcErrorToNativeError} = require('@aliceo2/web-ui'); const {Status} = require(path.join(__dirname, './../../protobuf/status_pb.js')); const {EnvironmentInfo} = require(path.join(__dirname, './../../protobuf/environmentinfo_pb.js')); const logger = (require('@aliceo2/web-ui').LogManager) diff --git a/Control/lib/controllers/Environment.controller.js b/Control/lib/controllers/Environment.controller.js index 2283f3f7c..a9cfcdedd 100644 --- a/Control/lib/controllers/Environment.controller.js +++ b/Control/lib/controllers/Environment.controller.js @@ -12,12 +12,12 @@ * or submit itself to any jurisdiction. */ const {LogManager, LogLevel} = require('@aliceo2/web-ui'); +const { + updateAndSendExpressResponseFromNativeError, grpcErrorToNativeError, InvalidInputError, UnauthorizedAccessError +} = require('@aliceo2/web-ui'); + const LOG_FACILITY = 'cog/env-ctrl'; const {EnvironmentTransitionType} = require('./../common/environmentTransitionType.enum.js'); -const {grpcErrorToNativeError} = require('./../errors/grpcErrorToNativeError.js'); -const {InvalidInputError} = require('./../errors/InvalidInputError.js'); -const {UnauthorizedAccessError} = require('./../errors/UnauthorizedAccessError.js'); -const {updateExpressResponseFromNativeError} = require('./../errors/updateExpressResponseFromNativeError.js'); const {User} = require('./../dtos/User.js'); /** @@ -64,7 +64,7 @@ class EnvironmentController { async getEnvironmentHandler(req, res) { const {id, source} = req.params; if (!id) { - updateExpressResponseFromNativeError(res, new InvalidInputError('Missing environment ID parameter')); + updateAndSendExpressResponseFromNativeError(res, new InvalidInputError('Missing environment ID parameter')); return; } try { @@ -72,7 +72,7 @@ class EnvironmentController { res.status(200).json(response); } catch (error) { this._logger.debug(error); - updateExpressResponseFromNativeError(res, error); + updateAndSendExpressResponseFromNativeError(res, error); } } @@ -88,9 +88,12 @@ class EnvironmentController { const {id} = req.params; const {type: transitionType, runNumber = ''} = req.body; if (!id) { - updateExpressResponseFromNativeError(res, new InvalidInputError('Missing environment ID parameter')); + updateAndSendExpressResponseFromNativeError(res, new InvalidInputError('Missing environment ID parameter')); } else if (!(transitionType in EnvironmentTransitionType)) { - updateExpressResponseFromNativeError(res, new InvalidInputError('Invalid environment transition to perform')); + updateAndSendExpressResponseFromNativeError( + res, + new InvalidInputError('Invalid environment transition to perform'), + ); } else { const transitionRequestedAt = Date.now(); let response = null; @@ -105,7 +108,7 @@ class EnvironmentController { `Request to transition environment by ${req.session.username} to ${transitionType} failed due to ${error}`, {level: LogLevel.OPERATIONS, system: 'GUI', facility: LOG_FACILITY, partition: id, run: runNumber} ); - updateExpressResponseFromNativeError(res, error); + updateAndSendExpressResponseFromNativeError(res, error); } const currentRunNumber = response?.currentRunNumber ?? runNumber; this._logger.debug(`${transitionType},${id},${currentRunNumber},${transitionRequestedAt},${Date.now()}`); @@ -125,7 +128,7 @@ class EnvironmentController { const {runNumber = '', keepTasks = false, allowInRunningState = false, force = false} = req.body ?? {}; if (!id) { - updateExpressResponseFromNativeError(res, new InvalidInputError('Missing environment ID parameter')); + updateAndSendExpressResponseFromNativeError(res, new InvalidInputError('Missing environment ID parameter')); } else { const destroyRequestedAt = Date.now(); this._logger.infoMessage(`Request to destroy environment by ${req.session.username} by force: ${force}`, @@ -139,7 +142,7 @@ class EnvironmentController { `Request to destroy environment by ${req.session.username} failed due to ${error}`, {level: LogLevel.OPERATIONS, system: 'GUI', facility: LOG_FACILITY, partition: id, run: runNumber} ); - updateExpressResponseFromNativeError(res, error); + updateAndSendExpressResponseFromNativeError(res, error); } this._logger.debug(`DESTROY_ENVIRONMENT,${id},${runNumber},${destroyRequestedAt},${Date.now()}`); } @@ -157,23 +160,29 @@ class EnvironmentController { const {detector, runType, configurationName} = req.body; if (!this._lockService.isLockOwnedByUser(detector, user)) { - updateExpressResponseFromNativeError(res, new UnauthorizedAccessError('Lock not taken')); + updateAndSendExpressResponseFromNativeError(res, new UnauthorizedAccessError('Lock not taken')); return; } if (!configurationName) { - updateExpressResponseFromNativeError(res, new InvalidInputError('Missing Configuration Name for deployment')); + updateAndSendExpressResponseFromNativeError( + res, + new InvalidInputError('Missing Configuration Name for deployment') + ); return; } try { const areDetectorsAvailable = await this._detectorService.areDetectorsAvailable([detector]); if (!areDetectorsAvailable) { - updateExpressResponseFromNativeError(res, new InvalidInputError(`Detector ${detector} is already active`)); + updateAndSendExpressResponseFromNativeError( + res, + new InvalidInputError(`Detector ${detector} is already active`) + ); return; } } catch (error) { - updateExpressResponseFromNativeError(res, grpcErrorToNativeError(error)); + updateAndSendExpressResponseFromNativeError(res, grpcErrorToNativeError(error)); return; } @@ -188,7 +197,7 @@ class EnvironmentController { } catch (error) { this._logger.debug(`Unable to retrieve saved configuration for ${configurationName} due to`); this._logger.debug(error); - updateExpressResponseFromNativeError(res, error); + updateAndSendExpressResponseFromNativeError(res, error); return; } @@ -199,7 +208,7 @@ class EnvironmentController { workflowTemplatePath = `${repository}/workflows/${template}@${revision}`; } catch (error) { this._logger.debug(`Unable to retrieve default workflow template due to ${error}`); - updateExpressResponseFromNativeError(res, error); + updateAndSendExpressResponseFromNativeError(res, error); return; } // Attempt to deploy environment @@ -216,7 +225,7 @@ class EnvironmentController { `Unable to deploy request by username(${username}) for ${configurationName} due to error`, {level: LogLevel.OPERATIONS, system: 'GUI', facility: LOG_FACILITY} ); - updateExpressResponseFromNativeError(res, error); + updateAndSendExpressResponseFromNativeError(res, error); } } } diff --git a/Control/lib/controllers/Lock.controller.js b/Control/lib/controllers/Lock.controller.js index dd6e6bcb3..bc9b77a11 100644 --- a/Control/lib/controllers/Lock.controller.js +++ b/Control/lib/controllers/Lock.controller.js @@ -12,10 +12,10 @@ * or submit itself to any jurisdiction. */ -const {InvalidInputError} = require('./../errors/InvalidInputError.js'); -const {DetectorLockAction} = require('./../common/lock/detectorLockAction.enum.js'); -const {LogManager, LogLevel} = require('@aliceo2/web-ui'); -const {updateExpressResponseFromNativeError} = require('./../errors/updateExpressResponseFromNativeError.js'); +const { LogManager, LogLevel } = require('@aliceo2/web-ui'); +const { updateAndSendExpressResponseFromNativeError, InvalidInputError } = require('@aliceo2/web-ui'); + +const { DetectorLockAction } = require('./../common/lock/detectorLockAction.enum.js'); const {User} = require('./../dtos/User.js'); const LOG_FACILITY = 'cog/log-ctrl'; @@ -48,7 +48,7 @@ class LockController { try { res.status(200).json(this._lockService.locksByDetectorToJSON()); } catch (error) { - updateExpressResponseFromNativeError(res, error); + updateAndSendExpressResponseFromNativeError(res, error); } } @@ -98,7 +98,7 @@ class LockController { } } catch (error) { this._logger.errorMessage(error, {level: LogLevel.DEVELOPER, facility: LOG_FACILITY}); - updateExpressResponseFromNativeError(res, error); + updateAndSendExpressResponseFromNativeError(res, error); } } diff --git a/Control/lib/controllers/Run.controller.js b/Control/lib/controllers/Run.controller.js index 8dbd89542..002f207a7 100644 --- a/Control/lib/controllers/Run.controller.js +++ b/Control/lib/controllers/Run.controller.js @@ -11,9 +11,8 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ -const {LogManager, LogLevel} = require('@aliceo2/web-ui'); +const {LogManager, LogLevel, updateAndSendExpressResponseFromNativeError} = require('@aliceo2/web-ui'); const LOG_FACILITY = 'run-ctrl'; -const {updateExpressResponseFromNativeError} = require('./../errors/updateExpressResponseFromNativeError.js'); const {CacheKeys} = require('./../common/cacheKeys.enum.js'); /** @@ -59,7 +58,7 @@ class RunController { res.status(200).json(calibrationRuns); } catch (error) { this._logger.debug(error); - updateExpressResponseFromNativeError(res, error); + updateAndSendExpressResponseFromNativeError(res, error); } } @@ -81,7 +80,7 @@ class RunController { const logMessage = `Error refreshing calibration configuration by ${req.session.username} due to: ${error}`; this._logger.errorMessage(logMessage, {level: LogLevel.OPERATIONS, facility: LOG_FACILITY}) - updateExpressResponseFromNativeError(res, error); + updateAndSendExpressResponseFromNativeError(res, error); } } } diff --git a/Control/lib/controllers/WorkflowTemplate.controller.js b/Control/lib/controllers/WorkflowTemplate.controller.js index 79550dbf0..2e8226908 100644 --- a/Control/lib/controllers/WorkflowTemplate.controller.js +++ b/Control/lib/controllers/WorkflowTemplate.controller.js @@ -11,7 +11,7 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ -const {updateExpressResponseFromNativeError} = require('./../errors/updateExpressResponseFromNativeError.js'); +const {updateAndSendExpressResponseFromNativeError, InvalidInputError} = require('@aliceo2/web-ui'); /** * Controller for dealing with all API requests on workflow templates from AliECS: @@ -40,7 +40,7 @@ class WorkflowTemplateController { const defaultTemplateSource = await this._workflowService.getDefaultTemplateSource(); res.status(200).json(defaultTemplateSource); } catch (error) { - updateExpressResponseFromNativeError(res, error); + updateAndSendExpressResponseFromNativeError(res, error); } } @@ -55,7 +55,7 @@ class WorkflowTemplateController { const mappings = await this._workflowService.retrieveWorkflowMappings(); res.status(200).json(mappings); } catch (error) { - updateExpressResponseFromNativeError(res, error); + updateAndSendExpressResponseFromNativeError(res, error); } } @@ -69,13 +69,16 @@ class WorkflowTemplateController { try { const {name} = req.query; if (!name) { - res.status(400).json({message: 'No name for the configuration provided'}); + updateAndSendExpressResponseFromNativeError( + res, + new InvalidInputError('No name for the configuration provided') + ); return; } const mappings = await this._workflowService.retrieveWorkflowSavedConfiguration(name); res.status(200).json(mappings); } catch (error) { - updateExpressResponseFromNativeError(res, error); + updateAndSendExpressResponseFromNativeError(res, error); } } } diff --git a/Control/lib/errors/InvalidInputError.js b/Control/lib/errors/InvalidInputError.js deleted file mode 100644 index 4ab9aab31..000000000 --- a/Control/lib/errors/InvalidInputError.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @license - * Copyright CERN and copyright holders of ALICE O2. This software is - * distributed under the terms of the GNU General Public License v3 (GPL - * Version 3), copied verbatim in the file "COPYING". - * - * See http://alice-o2.web.cern.ch/license for full licensing information. - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -/** - * Specific error to throw when an user provided input is not valid - */ -class InvalidInputError extends Error {} - -exports.InvalidInputError = InvalidInputError; diff --git a/Control/lib/errors/NotFoundError.js b/Control/lib/errors/NotFoundError.js deleted file mode 100644 index 41b0975fe..000000000 --- a/Control/lib/errors/NotFoundError.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @license - * Copyright CERN and copyright holders of ALICE O2. This software is - * distributed under the terms of the GNU General Public License v3 (GPL - * Version 3), copied verbatim in the file "COPYING". - * - * See http://alice-o2.web.cern.ch/license for full licensing information. - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -/** - * Specific error to throw when an item is expected to exist but it does not - */ -class NotFoundError extends Error {} - -exports.NotFoundError = NotFoundError ; diff --git a/Control/lib/errors/TimeoutError.js b/Control/lib/errors/TimeoutError.js deleted file mode 100644 index 477b2a0b3..000000000 --- a/Control/lib/errors/TimeoutError.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @license - * Copyright CERN and copyright holders of ALICE O2. This software is - * distributed under the terms of the GNU General Public License v3 (GPL - * Version 3), copied verbatim in the file "COPYING". - * - * See http://alice-o2.web.cern.ch/license for full licensing information. - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -/** - * Specific error to throw when an action timed out - */ -class TimeoutError extends Error {} - -exports.TimeoutError = TimeoutError; diff --git a/Control/lib/errors/UnauthorizedAccessError.js b/Control/lib/errors/UnauthorizedAccessError.js deleted file mode 100644 index 3e1c7f871..000000000 --- a/Control/lib/errors/UnauthorizedAccessError.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @license - * Copyright CERN and copyright holders of ALICE O2. This software is - * distributed under the terms of the GNU General Public License v3 (GPL - * Version 3), copied verbatim in the file "COPYING". - * - * See http://alice-o2.web.cern.ch/license for full licensing information. - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -/** - * Specific error to throw when an user does not have permissions for the action - */ -class UnauthorizedAccessError extends Error {} - -exports.UnauthorizedAccessError = UnauthorizedAccessError; diff --git a/Control/lib/errors/grpcErrorToNativeError.js b/Control/lib/errors/grpcErrorToNativeError.js deleted file mode 100644 index 3bb0a24c3..000000000 --- a/Control/lib/errors/grpcErrorToNativeError.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @license - * Copyright CERN and copyright holders of ALICE O2. This software is - * distributed under the terms of the GNU General Public License v3 (GPL - * Version 3), copied verbatim in the file "COPYING". - * - * See http://alice-o2.web.cern.ch/license for full licensing information. - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -const {NotFoundError} = require('./NotFoundError.js'); -const {InvalidInputError} = require('./InvalidInputError.js'); -const {TimeoutError} = require('./TimeoutError.js'); - -/** - * Convert a gRPC error to native error - * Code List source: https://grpc.github.io/grpc/core/md_doc_statuscodes.html - * - * @param {gRPCError} error - error object from gRPC Client library - * @returns {Error} - */ -const grpcErrorToNativeError = (error) => { - const { code, details } = error; - switch (code) { - case 3: - return new InvalidInputError(details); - case 4: - return new TimeoutError(details); - case 5: - return new NotFoundError(details); - default: - return new Error(details); - } -}; - -exports.grpcErrorToNativeError = grpcErrorToNativeError; diff --git a/Control/lib/errors/updateExpressResponseFromNativeError.js b/Control/lib/errors/updateExpressResponseFromNativeError.js deleted file mode 100644 index ede5666c1..000000000 --- a/Control/lib/errors/updateExpressResponseFromNativeError.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @license - * Copyright CERN and copyright holders of ALICE O2. This software is - * distributed under the terms of the GNU General Public License v3 (GPL - * Version 3), copied verbatim in the file "COPYING". - * - * See http://alice-o2.web.cern.ch/license for full licensing information. - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -const {UnauthorizedAccessError} = require('./UnauthorizedAccessError.js'); -const {InvalidInputError} = require('./InvalidInputError.js'); -const {NotFoundError} = require('./NotFoundError.js'); -const {TimeoutError} = require('./TimeoutError.js'); - -/** - * Update (in place) the given Express response considering a given error - * If the error is specific, the response status may be set to a specific error code - * - * @param {Response} response - express response to be used - * @param {Error} error - the error instance to handle - * @returns {void} - */ -const updateExpressResponseFromNativeError = (response, error) => { - let status = 500; - const {message, constructor} = error; - switch (constructor) { - case InvalidInputError: - status = 400; - break; - case UnauthorizedAccessError: - status = 403; - break; - case NotFoundError: - status = 404; - break; - case TimeoutError: - status = 408; - break; - } - response.status(status).json({message}); -}; - -exports.updateExpressResponseFromNativeError = updateExpressResponseFromNativeError; diff --git a/Control/lib/middleware/lockOwnership.middleware.js b/Control/lib/middleware/lockOwnership.middleware.js index 20d8571d8..bb9c25330 100644 --- a/Control/lib/middleware/lockOwnership.middleware.js +++ b/Control/lib/middleware/lockOwnership.middleware.js @@ -11,9 +11,8 @@ * or submit itself to any jurisdiction. */ +const {LogManager, updateAndSendExpressResponseFromNativeError} = require('@aliceo2/web-ui'); const {User} = require('../dtos/User'); -const {grpcErrorToNativeError} = require('../errors/grpcErrorToNativeError'); -const {updateExpressResponseFromNativeError} = require('../errors/updateExpressResponseFromNativeError'); /** * Middleware function to check that the user has ownership of the locks for the given detectors @@ -37,10 +36,11 @@ const lockOwnershipMiddleware = (lockService, environmentService) => { let detectors = []; try { const environment = await environmentService.getEnvironment(id); - detectors = environment.includedDetectors; + detectors = environment?.includedDetectors ?? []; } catch (error) { - console.error(error); - updateExpressResponseFromNativeError(res, grpcErrorToNativeError(error)); + const logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? 'cog'}/lock-ownership-middleware`); + logger.errorMessage(error); + updateAndSendExpressResponseFromNativeError(res, error); return; } try { @@ -50,8 +50,9 @@ const lockOwnershipMiddleware = (lockService, environmentService) => { next(); } } catch (error) { - console.error(error); - updateExpressResponseFromNativeError(res, error); + const logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? 'cog'}/lock-ownership-middleware`); + logger.errorMessage(error); + updateAndSendExpressResponseFromNativeError(res, error); } }; }; diff --git a/Control/lib/middleware/minimumRole.middleware.js b/Control/lib/middleware/minimumRole.middleware.js index bee85d2d7..1da88f6e2 100644 --- a/Control/lib/middleware/minimumRole.middleware.js +++ b/Control/lib/middleware/minimumRole.middleware.js @@ -12,9 +12,8 @@ * or submit itself to any jurisdiction. */ +const {UnauthorizedAccessError, updateAndSendExpressResponseFromNativeError} = require('@aliceo2/web-ui'); const {isRoleSufficient} = require('../common/role.enum.js'); -const {UnauthorizedAccessError} = require('../errors/UnauthorizedAccessError.js'); -const {updateExpressResponseFromNativeError} = require('../errors/updateExpressResponseFromNativeError.js'); /** * Method to receive a minimum role that needs to be met by owner of request and to return a middleware function @@ -41,7 +40,7 @@ const minimumRoleMiddleware = (minimumRole) => { } const isAllowed = accessList.some((role) => isRoleSufficient(role, minimumRole)); if (!isAllowed) { - updateExpressResponseFromNativeError(res, + updateAndSendExpressResponseFromNativeError(res, new UnauthorizedAccessError('Not enough permissions for this operation') ); return; @@ -49,7 +48,7 @@ const minimumRoleMiddleware = (minimumRole) => { next(); } catch (error) { console.error(error); - updateExpressResponseFromNativeError(res, error); + updateAndSendExpressResponseFromNativeError(res, error); } } }; diff --git a/Control/lib/services/Environment.service.js b/Control/lib/services/Environment.service.js index a4c2a7099..8d4c55cb3 100644 --- a/Control/lib/services/Environment.service.js +++ b/Control/lib/services/Environment.service.js @@ -12,11 +12,10 @@ * or submit itself to any jurisdiction. */ -const {NotFoundError} = require('@aliceo2/web-ui'); +const {grpcErrorToNativeError, NotFoundError} = require('@aliceo2/web-ui'); const {CacheKeys} = require('./../common/cacheKeys.enum.js'); const EnvironmentInfoAdapter = require('./../adapters/EnvironmentInfoAdapter.js'); const {EnvironmentTransitionResultAdapter} = require('./../adapters/EnvironmentTransitionResultAdapter.js'); -const {grpcErrorToNativeError} = require('./../errors/grpcErrorToNativeError.js'); /** * EnvironmentService class to be used to retrieve data from AliEcs Core via the gRPC Control client diff --git a/Control/lib/services/Lock.service.js b/Control/lib/services/Lock.service.js index dacc6600c..7ba5a4441 100644 --- a/Control/lib/services/Lock.service.js +++ b/Control/lib/services/Lock.service.js @@ -12,9 +12,8 @@ * or submit itself to any jurisdiction. */ +const {NotFoundError, UnauthorizedAccessError} = require('@aliceo2/web-ui'); const {DetectorLock} = require('./../dtos/DetectorLock.js'); -const {NotFoundError} = require('../errors/NotFoundError.js'); -const {UnauthorizedAccessError} = require('./../errors/UnauthorizedAccessError'); const PADLOCK_UPDATE = 'padlock-update'; diff --git a/Control/lib/services/Run.service.js b/Control/lib/services/Run.service.js index 604fd43c3..f8aaea6f4 100644 --- a/Control/lib/services/Run.service.js +++ b/Control/lib/services/Run.service.js @@ -12,10 +12,9 @@ * or submit itself to any jurisdiction. */ -const {LogManager, LogLevel} = require('@aliceo2/web-ui'); +const {LogManager, LogLevel, grpcErrorToNativeError} = require('@aliceo2/web-ui'); const {CacheKeys} = require('../common/cacheKeys.enum.js'); -const {grpcErrorToNativeError} = require('./../errors/grpcErrorToNativeError.js'); const {RunCalibrationStatus} = require('./../common/runCalibrationStatus.enum.js'); const {RunDefinitions} = require('./../common/runDefinition.enum.js') const {RUNTIME_COMPONENT: {COG}, RUNTIME_KEY: {CALIBRATION_MAPPING}} = require('./../common/kvStore/runtime.enum.js'); diff --git a/Control/lib/services/WorkflowTemplate.service.js b/Control/lib/services/WorkflowTemplate.service.js index f29b8c647..e67eb581e 100644 --- a/Control/lib/services/WorkflowTemplate.service.js +++ b/Control/lib/services/WorkflowTemplate.service.js @@ -12,8 +12,8 @@ * or submit itself to any jurisdiction. */ -const {grpcErrorToNativeError} = require('../errors/grpcErrorToNativeError.js'); -const {NotFoundError} = require('./../errors/NotFoundError.js'); +const {grpcErrorToNativeError, NotFoundError} = require('@aliceo2/web-ui'); + const RUNTIME_COMPONENT = 'COG'; const RUNTIME_CONFIGURATION = 'COG-v1'; const RUNTIME_KEY = 'workflow-mappings'; diff --git a/Control/package.json b/Control/package.json index 90bf4b458..c9783d7bc 100644 --- a/Control/package.json +++ b/Control/package.json @@ -39,11 +39,6 @@ "google-protobuf": "3.21.0", "kafkajs": "2.2.4" }, - "bundledDependencies": [ - "@aliceo2/web-ui", - "@grpc/grpc-js", - "@grpc/proto-loader" - ], "devDependencies": { "eslint": "^8.56.0", "jsonwebtoken": "^9.0.2", @@ -54,7 +49,7 @@ "sinon": "19.0.2", "supertest": "7.0.0" }, - "bundleDependencies": [ + "bundledDependencies": [ "@aliceo2/web-ui", "@grpc/grpc-js", "@grpc/proto-loader" diff --git a/Control/test/api/lock/api-put-locks.test.js b/Control/test/api/lock/api-put-locks.test.js index 9f5767e7b..a7158e153 100644 --- a/Control/test/api/lock/api-put-locks.test.js +++ b/Control/test/api/lock/api-put-locks.test.js @@ -38,7 +38,9 @@ describe(`'API - PUT - /locks/:action/:detectorId' test suite`, () => { await request(`${TEST_URL}/api/locks`) .put(`/${DetectorLockAction.TAKE}/MID?token=${ADMIN_TEST_TOKEN}`) .expect(403, { - message: 'Unauthorized TAKE action for lock of detector MID by user Admin User' + message: 'Unauthorized TAKE action for lock of detector MID by user Admin User', + title: 'Unauthorized Access', + status: 403, }); }); @@ -46,7 +48,9 @@ describe(`'API - PUT - /locks/:action/:detectorId' test suite`, () => { await request(`${TEST_URL}/api/locks`) .put(`/${DetectorLockAction.TAKE}/DCS?token=${GUEST_TEST_TOKEN}`) .expect(403, { - message: 'Not enough permissions for this operation' + message: 'Not enough permissions for this operation', + title: 'Unauthorized Access', + status: 403, }); }); @@ -114,7 +118,9 @@ describe(`'API - PUT - /locks/:action/:detectorId' test suite`, () => { await request(`${TEST_URL}/api/locks`) .put(`/${DetectorLockAction.RELEASE}/MID?token=${GUEST_TEST_TOKEN}`) .expect(403, { - message: 'Not enough permissions for this operation' + message: 'Not enough permissions for this operation', + status: 403, + title: 'Unauthorized Access', }); }); @@ -122,7 +128,9 @@ describe(`'API - PUT - /locks/:action/:detectorId' test suite`, () => { await request(`${TEST_URL}/api/locks`) .put(`/${DetectorLockAction.RELEASE}/MID?token=${DET_MID_TEST_TOKEN}`) .expect(403, { - message: 'Unauthorized RELEASE action for lock of detector MID by user Detector User' + message: 'Unauthorized RELEASE action for lock of detector MID by user Detector User', + status: 403, + title: 'Unauthorized Access', }); }); diff --git a/Control/test/lib/controllers/mocha-environment.controller.test.js b/Control/test/lib/controllers/mocha-environment.controller.test.js index 4fdf80758..9ebf3d5a4 100644 --- a/Control/test/lib/controllers/mocha-environment.controller.test.js +++ b/Control/test/lib/controllers/mocha-environment.controller.test.js @@ -15,9 +15,9 @@ const assert = require('assert'); const sinon = require('sinon'); +const {NotFoundError} = require('@aliceo2/web-ui'); const {EnvironmentController} = require('../../../lib/controllers/Environment.controller.js'); -const {NotFoundError} = require('../../../lib/errors/NotFoundError.js'); describe('EnvironmentController test suite', () => { const ENVIRONMENT_NOT_FOUND_ID = '2432ENV404'; @@ -63,27 +63,47 @@ describe('EnvironmentController test suite', () => { it('should respond with error if provided environment id cannot be found', async () => { await envCtrl.getEnvironmentHandler({params: {id: ENVIRONMENT_NOT_FOUND_ID}}, res); assert.ok(res.status.calledWith(404)); - assert.ok(res.json.calledWith({message: `Environment with ID: ${ENVIRONMENT_NOT_FOUND_ID} could not be found`})); + assert.ok(res.json.calledWith({ + message: `Environment with ID: ${ENVIRONMENT_NOT_FOUND_ID} could not be found`, + status: 404, + title: 'Not Found', + })); }); it('should respond with error if service for retrieving information failed', async () => { await envCtrl.getEnvironmentHandler({params: {id: ENVIRONMENT_ID_FAILED_TO_RETRIEVE}}, res); assert.ok(res.status.calledWith(500)); - assert.ok(res.json.calledWith({message: `Data service failed`})); + assert.ok(res.json.calledWith({ + message: 'Data service failed', + status: 500, + title: 'Unknown Error', + })); }); it('should respond with error if client did not provide valid request for ID', async () => { await envCtrl.getEnvironmentHandler({params: {id: null}}, res); assert.ok(res.status.calledWith(400)); - assert.ok(res.json.calledWith({message: `Missing environment ID parameter`})); + assert.ok(res.json.calledWith({ + message: 'Missing environment ID parameter', + status: 400, + title: 'Invalid Input', + })); await envCtrl.getEnvironmentHandler({params: {}}, res); assert.ok(res.status.calledWith(400)); - assert.ok(res.json.calledWith({message: `Missing environment ID parameter`})); + assert.ok(res.json.calledWith({ + message: 'Missing environment ID parameter', + status: 400, + title: 'Invalid Input', + })); await envCtrl.getEnvironmentHandler({params: {id: ''}}, res); assert.ok(res.status.calledWith(400)); - assert.ok(res.json.calledWith({message: `Missing environment ID parameter`})); + assert.ok(res.json.calledWith({ + message: 'Missing environment ID parameter', + status: 400, + title: 'Invalid Input', + })); }); }); @@ -98,25 +118,41 @@ describe('EnvironmentController test suite', () => { it('should return error due to missing id', async () => { await envCtrl.transitionEnvironmentHandler({params: {id: null}, body: {type: null}, session: {username: '', personid: 0}}, res); assert.ok(res.status.calledWith(400)); - assert.ok(res.json.calledWith({message: `Missing environment ID parameter`})); + assert.ok(res.json.calledWith({ + message: 'Missing environment ID parameter', + status: 400, + title: 'Invalid Input', + })); }); it('should return error due to missing transition type', async () => { await envCtrl.transitionEnvironmentHandler({params: {id: 'ABC123'}, body: {type: null}, session: {username: '', personid: 0}}, res); assert.ok(res.status.calledWith(400)); - assert.ok(res.json.calledWith({message: `Invalid environment transition to perform`})); + assert.ok(res.json.calledWith({ + message: 'Invalid environment transition to perform', + status: 400, + title: 'Invalid Input', + })); }); it('should return error due to invalid transition type', async () => { await envCtrl.transitionEnvironmentHandler({params: {id: 'ABC123'}, body: {type: 'NON_EXISTENT'}, session: {username: '', personid: 0}}, res); assert.ok(res.status.calledWith(400)); - assert.ok(res.json.calledWith({message: `Invalid environment transition to perform`})); + assert.ok(res.json.calledWith({ + message: 'Invalid environment transition to perform', + status: 400, + title: 'Invalid Input', + })); }); it('should return error due to transition environment issue', async () => { await envCtrl.transitionEnvironmentHandler({session: {username: 'test', personid: 0}, params: {id: ENVIRONMENT_ID_FAILED_TO_RETRIEVE}, body: {type: 'START_ACTIVITY'}}, res); assert.ok(res.status.calledWith(500)); - assert.ok(res.json.calledWith({message: `Cannot transition environment`})); + assert.ok(res.json.calledWith({ + message: 'Cannot transition environment', + status: 500, + title: 'Unknown Error', + })); }); it('should successfully return environment in transition state', async () => { @@ -137,13 +173,21 @@ describe('EnvironmentController test suite', () => { it('should return error due to missing id when attempting to destroy environment', async () => { await envCtrl.destroyEnvironmentHandler({session: {username: 'test'}, params: {id: null}, body: {type: null}}, res); assert.ok(res.status.calledWith(400)); - assert.ok(res.json.calledWith({message: `Missing environment ID parameter`})); + assert.ok(res.json.calledWith({ + message: 'Missing environment ID parameter', + status: 400, + title: 'Invalid Input', + })); }); it('should return error due to destroy environment issue', async () => { await envCtrl.destroyEnvironmentHandler({session: {username: 'test'}, params: {id: ENVIRONMENT_ID_FAILED_TO_RETRIEVE}, body: {}}, res); assert.ok(res.status.calledWith(500)); - assert.ok(res.json.calledWith({message: `Cannot destroy environment`})); + assert.ok(res.json.calledWith({ + message: 'Cannot destroy environment', + status: 500, + title: 'Unknown Error', + })); }); it('should successfully return environment following destroy action', async () => { diff --git a/Control/test/lib/controllers/mocha-lock.controller.test.js b/Control/test/lib/controllers/mocha-lock.controller.test.js index dabab2536..0b5377c03 100644 --- a/Control/test/lib/controllers/mocha-lock.controller.test.js +++ b/Control/test/lib/controllers/mocha-lock.controller.test.js @@ -103,7 +103,11 @@ describe(`'LockController' test suite`, () => { } }, res); assert.ok(res.status.calledWith(403)); - assert.ok(res.json.calledWith({message: `Unauthorized TAKE action for lock of detector ABC by user NotAnonymous`})); + assert.ok(res.json.calledWith({ + message: 'Unauthorized TAKE action for lock of detector ABC by user NotAnonymous', + status: 403, + title: 'Unauthorized Access', + })); }); it('should reply with error when an already held lock is requested to be released by another user without force', () => { @@ -118,7 +122,11 @@ describe(`'LockController' test suite`, () => { } }, res); assert.ok(res.status.calledWith(403)); - assert.ok(res.json.calledWith({message: `Unauthorized RELEASE action for lock of detector ABC by user NotAnonymous`})); + assert.ok(res.json.calledWith({ + message: 'Unauthorized RELEASE action for lock of detector ABC by user NotAnonymous', + title: 'Unauthorized Access', + status: 403, + })); }); it('should successfully reply to a request to release lock held by correct owner', () => { @@ -158,7 +166,11 @@ describe(`'LockController' test suite`, () => { } }, res); assert.ok(res.status.calledWith(400)); - assert.ok(res.json.calledWith({message: 'Missing detectorId'})); + assert.ok(res.json.calledWith({ + message: 'Missing detectorId', + status: 400, + title: 'Invalid Input', + })); lockController.actionLockHandler({ params: { @@ -171,7 +183,11 @@ describe(`'LockController' test suite`, () => { } }, res); assert.ok(res.status.calledWith(400)); - assert.ok(res.json.calledWith({message: 'Invalid action to apply on lock for detector: ABC'})); + assert.ok(res.json.calledWith({ + message: 'Invalid action to apply on lock for detector: ABC', + status: 400, + title: 'Invalid Input', + })); }); it('should return 404 and not found error message for a detector that does not exist', () => { @@ -186,7 +202,11 @@ describe(`'LockController' test suite`, () => { } }, res); assert.ok(res.status.calledWith(404)); - assert.ok(res.json.calledWith({message: 'Detector NONEXISTENT not found in the list of detectors'})); + assert.ok(res.json.calledWith({ + message: 'Detector NONEXISTENT not found in the list of detectors', + status: 404, + title: 'Not Found', + })); }); it('should successfully lock all detectors available to lock', () => { diff --git a/Control/test/lib/controllers/mocha-run-controller.test.js b/Control/test/lib/controllers/mocha-run-controller.test.js index 51c55d9e8..0aaaae5b3 100644 --- a/Control/test/lib/controllers/mocha-run-controller.test.js +++ b/Control/test/lib/controllers/mocha-run-controller.test.js @@ -17,7 +17,7 @@ const assert = require('assert'); const sinon = require('sinon'); const {RunController} = require('../../../lib/controllers/Run.controller.js'); -const {TimeoutError} = require('../../../lib/errors/TimeoutError.js'); +const {TimeoutError} = require('@aliceo2/web-ui'); describe(`'RunController' test suite`, () => { const res = { @@ -79,7 +79,11 @@ describe(`'RunController' test suite`, () => { ); await runController.getCalibrationRunsHandler({}, res); assert.ok(res.status.calledWith(500)); - assert.ok(res.json.calledWith({message: 'Unable to retrieve such runs'})); + assert.ok(res.json.calledWith({ + message: 'Unable to retrieve such runs', + status: 500, + title: 'Unknown Error' + })); }); }); @@ -102,7 +106,11 @@ describe(`'RunController' test suite`, () => { ); await runController.refreshCalibrationRunsConfigurationHandler({session: {username: 'test'}}, res); assert.ok(res.status.calledWith(408)); - assert.ok(res.json.calledWith({message: 'Request Expired'})); + assert.ok(res.json.calledWith({ + message: 'Request Expired', + status: 408, + title: 'Timeout' + })); }); }); }); diff --git a/Control/test/lib/controllers/mocha-workflowTemplate.controller.test.js b/Control/test/lib/controllers/mocha-workflowTemplate.controller.test.js index e4fe440f6..b18bd8df2 100644 --- a/Control/test/lib/controllers/mocha-workflowTemplate.controller.test.js +++ b/Control/test/lib/controllers/mocha-workflowTemplate.controller.test.js @@ -15,9 +15,9 @@ const assert = require('assert'); const sinon = require('sinon'); +const {NotFoundError} = require('@aliceo2/web-ui'); const {WorkflowTemplateController} = require('../../../lib/controllers/WorkflowTemplate.controller.js'); -const {NotFoundError} = require('../../../lib/errors/NotFoundError.js'); describe('WorkflowController test suite', () => { const res = { @@ -41,7 +41,11 @@ describe('WorkflowController test suite', () => { }); await workflowCtrl.getDefaultTemplateSource({}, res); assert.ok(res.status.calledWith(404)); - assert.ok(res.json.calledWith({message: 'No default revision identified'})); + assert.ok(res.json.calledWith({ + message: 'No default revision identified', + status: 404, + title: 'Not Found' + })); }); }); @@ -70,7 +74,11 @@ describe('WorkflowController test suite', () => { }); await workflowCtrl.getWorkflowMapping({}, res); assert.ok(res.status.calledWith(404)); - assert.ok(res.json.calledWith({message: 'No mappings found'})); + assert.ok(res.json.calledWith({ + message: 'No mappings found', + status: 404, + title: 'Not Found' + })); }); it('should return 502 response as there was specific error provided', async () => { @@ -79,7 +87,11 @@ describe('WorkflowController test suite', () => { }); await workflowCtrl.getWorkflowMapping({}, res); assert.ok(res.status.calledWith(500)); - assert.ok(res.json.calledWith({message: 'No mappings found'})); + assert.ok(res.json.calledWith({ + message: 'No mappings found', + status: 500, + title: 'Unknown Error' + })); }); }); @@ -97,7 +109,11 @@ describe('WorkflowController test suite', () => { const workflowCtrl = new WorkflowTemplateController({}); await workflowCtrl.getWorkflowConfiguration({query:{}}, res); assert.ok(res.status.calledWith(400)); - assert.ok(res.json.calledWith({message: 'No name for the configuration provided'})); + assert.ok(res.json.calledWith({ + message: 'No name for the configuration provided', + status: 400, + title: 'Invalid Input' + })); }); it('should return 404 response as there was no default revision found', async () => { @@ -106,7 +122,11 @@ describe('WorkflowController test suite', () => { }); await workflowCtrl.getWorkflowConfiguration({query: {name: 'test'}}, res); assert.ok(res.status.calledWith(404)); - assert.ok(res.json.calledWith({message: 'No configuration found'})); + assert.ok(res.json.calledWith({ + message: 'No configuration found', + status: 404, + title: 'Not Found' + })); }); }); }); diff --git a/Control/test/lib/middleware/mocha-lockOwnership.middleware.test.js b/Control/test/lib/middleware/mocha-lockOwnership.middleware.test.js index f09af73a5..f7eac0908 100644 --- a/Control/test/lib/middleware/mocha-lockOwnership.middleware.test.js +++ b/Control/test/lib/middleware/mocha-lockOwnership.middleware.test.js @@ -14,6 +14,8 @@ const assert = require('assert'); const sinon = require('sinon'); +const { NotFoundError, TimeoutError } = require('@aliceo2/web-ui'); + const {lockOwnershipMiddleware} = require('../../../lib/middleware/lockOwnership.middleware'); const {LockService} = require('../../../lib/services/Lock.service.js'); const {EnvironmentService} = require('../../../lib/services/Environment.service.js'); @@ -42,16 +44,16 @@ describe('`LockOwnership` middleware test suite', () => { }; const environmentServiceStub = sinon.createStubInstance(EnvironmentService, { - getEnvironment: sinon.stub().rejects({ - code: 5, - details: 'Environment not found', - }) + getEnvironment: sinon.stub().rejects(new NotFoundError('Environment not found')) }); await lockOwnershipMiddleware(null, environmentServiceStub)(req, res); assert.ok(res.status.calledWith(404)); - assert.ok(res.json.calledWith( - {message: 'Environment not found'} + assert.ok(res.json.calledWith({ + message: 'Environment not found', + status: 404, + title: 'Not Found' + } )); }); @@ -63,16 +65,16 @@ describe('`LockOwnership` middleware test suite', () => { }; const environmentServiceStub = sinon.createStubInstance(EnvironmentService, { - getEnvironment: sinon.stub().rejects({ - code: 5, - details: 'Environment not found', - }) + getEnvironment: sinon.stub().rejects(new NotFoundError('Environment not found')) }); await lockOwnershipMiddleware(null, environmentServiceStub)(req, res); assert.ok(res.status.calledWith(404)); - assert.ok(res.json.calledWith( - {message: 'Environment not found'} + assert.ok(res.json.calledWith({ + message: 'Environment not found', + status: 404, + title: 'Not Found', + } )); }); @@ -84,16 +86,17 @@ describe('`LockOwnership` middleware test suite', () => { }; const environmentServiceStub = sinon.createStubInstance(EnvironmentService, { - getEnvironment: sinon.stub().rejects({ - code: 4, - details: 'Operation timeout', - }) + getEnvironment: sinon.stub().rejects(new TimeoutError('Operation timeout')) }); await lockOwnershipMiddleware(null, environmentServiceStub)(req, res); assert.ok(res.status.calledWith(408)); assert.ok(res.json.calledWith( - {message: 'Operation timeout'} + { + message: 'Operation timeout', + status: 408, + title: 'Timeout' + } )); }); diff --git a/Control/test/lib/middleware/mocha-minimumRole.middleware.test.js b/Control/test/lib/middleware/mocha-minimumRole.middleware.test.js index 66a236d8a..33094a4f2 100644 --- a/Control/test/lib/middleware/mocha-minimumRole.middleware.test.js +++ b/Control/test/lib/middleware/mocha-minimumRole.middleware.test.js @@ -40,7 +40,11 @@ describe('`Role Middleware` test suite', () => { }; minimumRoleMiddleware(Role.ADMIN)(req, res, null); assert.ok(res.status.calledWith(403)); - assert.ok(res.json.calledWith({message: 'Not enough permissions for this operation'})) + assert.ok(res.json.calledWith({ + message: 'Not enough permissions for this operation', + status: 403, + title: 'Unauthorized Access', + })) }); it('should update HTTP response object with 403 status error if missing access', () => { diff --git a/Control/test/lib/services/mocha-environment.service.test.js b/Control/test/lib/services/mocha-environment.service.test.js index e7c935319..16995fcfb 100644 --- a/Control/test/lib/services/mocha-environment.service.test.js +++ b/Control/test/lib/services/mocha-environment.service.test.js @@ -15,9 +15,9 @@ const assert = require('assert'); const sinon = require('sinon'); +const { NotFoundError } = require('@aliceo2/web-ui'); const {EnvironmentService} = require('./../../../lib/services/Environment.service.js'); -const { NotFoundError } = require('./../../../lib/errors/NotFoundError.js'); const { User } = require('./../../../lib/dtos/User.js'); describe('EnvironmentService test suite', () => { diff --git a/Control/test/lib/services/mocha-lock.service.test.js b/Control/test/lib/services/mocha-lock.service.test.js index cbbb38a07..7982bc810 100644 --- a/Control/test/lib/services/mocha-lock.service.test.js +++ b/Control/test/lib/services/mocha-lock.service.test.js @@ -13,10 +13,10 @@ */ const assert = require('assert'); +const {NotFoundError, UnauthorizedAccessError} = require('@aliceo2/web-ui'); + const {DetectorLock} = require('./../../../lib/dtos/DetectorLock.js'); const {LockService} = require('./../../../lib/services/Lock.service.js'); -const {NotFoundError} = require('../../../lib/errors/NotFoundError.js'); -const {UnauthorizedAccessError} = require('../../../lib/errors/UnauthorizedAccessError.js'); const {User} = require('./../../../lib/dtos/User.js'); describe(`'LockService' test suite`, () => { diff --git a/Control/test/lib/services/mocha-run.service.test.js b/Control/test/lib/services/mocha-run.service.test.js index cc5890e16..04181ca54 100644 --- a/Control/test/lib/services/mocha-run.service.test.js +++ b/Control/test/lib/services/mocha-run.service.test.js @@ -15,10 +15,10 @@ const assert = require('assert'); const sinon = require('sinon'); +const {NotFoundError} = require('@aliceo2/web-ui'); const {RunDefinitions} = require('./../../../lib/common/runDefinition.enum.js'); const {RunService} = require('./../../../lib/services/Run.service.js'); -const {NotFoundError} = require('./../../../lib/errors/NotFoundError.js'); describe(`'RunService' test suite`, async () => { describe(`'_retrieveCalibrationConfigurationsForDetectors test suite`, async () => { diff --git a/Control/test/lib/services/mocha-workflowTemplate.service.test.js b/Control/test/lib/services/mocha-workflowTemplate.service.test.js index e63442218..61b4cf2b3 100644 --- a/Control/test/lib/services/mocha-workflowTemplate.service.test.js +++ b/Control/test/lib/services/mocha-workflowTemplate.service.test.js @@ -15,9 +15,9 @@ const assert = require('assert'); const sinon = require('sinon'); +const {NotFoundError} = require('@aliceo2/web-ui'); const {WorkflowTemplateService} = require('../../../lib/services/WorkflowTemplate.service.js'); -const {NotFoundError} = require('../../../lib/errors/NotFoundError.js'); const mockRepoList = [ { diff --git a/Control/test/public/page-new-environment-mocha.js b/Control/test/public/page-new-environment-mocha.js index feb28c23e..3764df79d 100644 --- a/Control/test/public/page-new-environment-mocha.js +++ b/Control/test/public/page-new-environment-mocha.js @@ -432,7 +432,7 @@ describe('`pageNewEnvironment` test-suite', async () => { it('should successfully create a new environment', async () => { await page.locator('#deploy-env') - .setTimeout(500) + .setTimeout(1000) .click(); await page.waitForNavigation({ waitUntil: 'networkidle0',