diff --git a/Control/lib/adapters/RunSummaryAdapter.js b/Control/lib/adapters/RunSummaryAdapter.js index 3bb367fce..a0e0923bb 100644 --- a/Control/lib/adapters/RunSummaryAdapter.js +++ b/Control/lib/adapters/RunSummaryAdapter.js @@ -19,7 +19,7 @@ class RunSummaryAdapter { /** * RunSummaryAdapter */ - constructor() {} + constructor() { } /** * Converts the given object to an entity object. @@ -36,16 +36,22 @@ class RunSummaryAdapter { runType, startTime, endTime, - detectors = [], } = run; - return { + + let {detectors = []} = run; + if (typeof detectors === 'string') { + detectors = detectors.split(','); + } + detectors.sort(); + + return { runNumber, environmentId, definition, calibrationStatus, runType: runType?.name, startTime, - detectors: detectors.sort(), + detectors, endTime, }; } diff --git a/Control/lib/api.js b/Control/lib/api.js index d772bdb08..25f3cb8d0 100644 --- a/Control/lib/api.js +++ b/Control/lib/api.js @@ -16,14 +16,21 @@ const log = new (require('@aliceo2/web-ui').Log)(`${process.env.npm_config_log_l const config = require('./config/configProvider.js'); // controllers +const {ConsulController} = require('./controllers/Consul.controller.js'); +const {EnvironmentController} = require('./controllers/Environment.controller.js'); +const {RunController} = require('./controllers/Run.controller.js'); const {StatusController} = require('./controllers/Status.controller.js'); const {WebSocketService} = require('./services/WebSocket.service.js'); -const {ConsulController} = require('./controllers/Consul.controller.js'); +const {WorkflowTemplateController} = require('./controllers/WorkflowTemplate.controller.js'); // local services +const {BookkeepingService} = require('./services/Bookkeeping.service.js'); +const {EnvironmentService} = require('./services/Environment.service.js'); +const {Intervals} = require('./services/Intervals.service.js'); const Lock = require('./services/Lock.js'); +const {RunService} = require('./services/Run.service.js'); const {StatusService} = require('./services/Status.service.js'); -const {Intervals} = require('./services/Intervals.service.js'); +const {WorkflowTemplateService} = require('./services/WorkflowTemplate.service.js'); // web-ui services const {NotificationService, ConsulService} = require('@aliceo2/web-ui'); @@ -34,13 +41,8 @@ const ControlService = require('./control-core/ControlService.js'); const ApricotService = require('./control-core/ApricotService.js'); const AliecsRequestHandler = require('./control-core/RequestHandler.js'); const EnvCache = require('./control-core/EnvCache.js'); -const {EnvironmentService} = require('./services/Environment.service.js'); -const {WorkflowTemplateService} = require('./services/WorkflowTemplate.service.js'); - -const {EnvironmentController} = require('./controllers/Environment.controller.js'); const path = require('path'); -const {WorkflowTemplateController} = require('./controllers/WorkflowTemplate.controller.js'); const O2_CONTROL_PROTO_PATH = path.join(__dirname, './../protobuf/o2control.proto'); const O2_APRICOT_PROTO_PATH = path.join(__dirname, './../protobuf/o2apricot.proto'); @@ -86,6 +88,11 @@ module.exports.setup = (http, ws) => { const envCache = new EnvCache(ctrlService); envCache.setWs(ws); + const bkpService = new BookkeepingService(config.bookkeeping ?? {}); + const runService = new RunService(bkpService, apricotService); + runService.init(); + const runController = new RunController(runService); + const notificationService = new NotificationService(config.kafka); if (notificationService.isConfigured()) { notificationService.proxyWebNotificationToWs(ws); @@ -115,6 +122,8 @@ module.exports.setup = (http, ws) => { http.get('/workflow/template/mappings', workflowController.getWorkflowMapping.bind(workflowController)) http.get('/workflow/configuration', workflowController.getWorkflowConfiguration.bind(workflowController)); + http.get('/runs/calibration', runController.getCalibrationRunsHandler.bind(runController)) + http.get('/environment/:id/:source?', coreMiddleware, envCtrl.getEnvironment.bind(envCtrl), {public: true}); http.get('/core/environments', coreMiddleware, (req, res) => envCache.get(req, res), {public: true}); http.post('/core/environments/configuration/save', (req, res) => apricotService.saveCoreEnvConfig(req, res)); diff --git a/Control/lib/common/kvStore/runtime.enum.js b/Control/lib/common/kvStore/runtime.enum.js index 416fcdeff..18ea7374e 100644 --- a/Control/lib/common/kvStore/runtime.enum.js +++ b/Control/lib/common/kvStore/runtime.enum.js @@ -22,7 +22,6 @@ const RUNTIME_KEY = Object.freeze({ FLP_VERSION: 'flp_suite_version', PDP_VERSION: 'pdp_o2pdpsuite_version', CALIBRATION_MAPPING: 'calibration-mappings', - }); module.exports = {RUNTIME_COMPONENT, RUNTIME_KEY}; diff --git a/Control/lib/common/runDefinition.enum.js b/Control/lib/common/runDefinition.enum.js new file mode 100644 index 000000000..3ddde1732 --- /dev/null +++ b/Control/lib/common/runDefinition.enum.js @@ -0,0 +1,22 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * 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. +*/ + +/** + * Run Definitions as per Bookkeeping's implementation: https://github.com/AliceO2Group/Bookkeeping/blob/main/docs/RUN_DEFINITIONS.md + */ +const RUN_DEFINITIONS = Object.freeze({ + CALIBRATION: 'CALIBRATION' +}); + +exports.RUN_DEFINITIONS = RUN_DEFINITIONS; diff --git a/Control/lib/control-core/RequestHandler.js b/Control/lib/control-core/RequestHandler.js index f8ababb71..2125e77d6 100644 --- a/Control/lib/control-core/RequestHandler.js +++ b/Control/lib/control-core/RequestHandler.js @@ -74,8 +74,6 @@ class RequestHandler { variables.odc_n_epns = odc_n_epns; } req.body.vars = variables; - console.log('-------------------------'); - console.log(req.body.vars); } catch (error) { console.error(error); } diff --git a/Control/lib/controllers/Run.controller.js b/Control/lib/controllers/Run.controller.js new file mode 100644 index 000000000..ecc0c4f3a --- /dev/null +++ b/Control/lib/controllers/Run.controller.js @@ -0,0 +1,51 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * 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 {Log} = require('@aliceo2/web-ui'); +const {updateExpressResponseFromNativeError} = require('./../errors/updateExpressResponseFromNativeError.js'); + +/** + * Controller for dealing with all API requests on retrieving information on runs + */ +class RunController { + /** + * Constructor for initializing controller of runs + * @param {RunService} runService - service to use to build information on runs + */ + constructor(runService) { + this._logger = new Log(`${process.env.npm_config_log_label ?? 'cog'}/run-ctrl`); + + /** + * @type {RunService} + */ + this._runService = runService; + } + + /** + * API - GET endpoint for retrieving calibration runs + * @param {Request} req - HTTP Request object + * @param {Response} res - HTTP Response object + * @returns {void} + */ + async getCalibrationRunsHandler(_, res) { + try { + const response = await this._runService.retrieveCalibrationRunsGroupedByDetector(); + res.status(200).json(response); + } catch (error) { + this._logger.debug(error); + updateExpressResponseFromNativeError(res, error); + } + } +} + +module.exports = {RunController}; diff --git a/Control/lib/errors/updateExpressResponseFromNativeError.js b/Control/lib/errors/updateExpressResponseFromNativeError.js index 4ead03474..a711fac55 100644 --- a/Control/lib/errors/updateExpressResponseFromNativeError.js +++ b/Control/lib/errors/updateExpressResponseFromNativeError.js @@ -24,7 +24,7 @@ const {TimeoutError} = require('./TimeoutError.js'); * @returns {void} */ const updateExpressResponseFromNativeError = (response, error) => { - let status = 502; + let status = 500; const {message, constructor} = error; switch (constructor) { case InvalidInputError: @@ -36,10 +36,7 @@ const updateExpressResponseFromNativeError = (response, error) => { case TimeoutError: status = 408; break; - default: - status = 502; } - response.status(status).json({message}); }; diff --git a/Control/lib/services/Bookkeeping.service.js b/Control/lib/services/Bookkeeping.service.js index acaf04449..2b30d0049 100644 --- a/Control/lib/services/Bookkeeping.service.js +++ b/Control/lib/services/Bookkeeping.service.js @@ -26,7 +26,7 @@ class BookkeepingService { */ constructor({url = '', token = ''}) { this._url = url; - const {protocol, hostname, port} = new URL(this._url); + const {protocol, hostname, port} = url ? new URL(this._url) : {}; this._hostname = hostname; this._port = port; this._protocol = protocol; @@ -57,7 +57,7 @@ class BookkeepingService { } catch (error) { this._logger.debug(error); } - return {}; + return null; } /** diff --git a/Control/lib/services/CalibrationRun.service.js b/Control/lib/services/Run.service.js similarity index 59% rename from Control/lib/services/CalibrationRun.service.js rename to Control/lib/services/Run.service.js index dbe215902..44c4427a4 100644 --- a/Control/lib/services/CalibrationRun.service.js +++ b/Control/lib/services/Run.service.js @@ -16,16 +16,17 @@ const {Log} = require('@aliceo2/web-ui'); const {grpcErrorToNativeError} = require('./../errors/grpcErrorToNativeError.js'); const {RUNTIME_COMPONENT: {COG}, RUNTIME_KEY: {CALIBRATION_MAPPING}} = require('./../common/kvStore/runtime.enum.js'); -const {LOG_LEVEL} = require('./../common/logLevel.enum.js'); +const {RUN_DEFINITIONS} = require('./../common/runDefinition.enum.js') +const {LOG_LEVEL} = require('../common/logLevel.enum.js'); /** * @class - * CalibrationRunService class to be used for retrieving and building information needed for the CalibrationRun page: + * RunService class to be used for retrieving and building information on runs(active/previous) from Bookkeeping: * * store in-memory information with regards to runTypes(name-id mapping), calibration per detector mappings, etc. * * displaying latest calibration runs as per mapping defined in KV Store * * allowing user to deploy calibration runs and follow their progress via streams */ -class CalibrationRunService { +class RunService { /** * @constructor * Constructor for configuring the service to retrieve data via passed services @@ -51,9 +52,9 @@ class CalibrationRunService { /** * @type {Object>} */ - this._calibrationPerDetectorMap = []; + this._calibrationPerDetectorMap = {}; - this._logger = new Log(`${process.env.npm_config_log_label ?? 'cog'}/calibration-service`); + this._logger = new Log(`${process.env.npm_config_log_label ?? 'cog'}/run-service`); } /** @@ -62,21 +63,52 @@ class CalibrationRunService { */ async init() { this._runTypes = await this._bkpService.getRunTypes(); + this._calibrationPerDetectorMap = await this._retrieveCalibrationForDetector(); + this._calibrationRunsPerDetector = await this.retrieveCalibrationRunsGroupedByDetector(); + } + + /** + * Based on already loaded calibration configuration mapping from KV store, retrieve runs with those characteristics from Bookkeeping + * @return {Promise.Error>} - list of calibration runs grouped by detector + */ + async retrieveCalibrationRunsGroupedByDetector() { + const calibrationRunsPerDetector = {}; + for (const detector in this._calibrationPerDetectorMap) { + const runTypesPerDetector = this._calibrationPerDetectorMap[detector] ?? []; + calibrationRunsPerDetector[detector] = []; + for (const runType of runTypesPerDetector) { + const runTypeId = this._runTypes[runType]; + const runInfo = await this._bkpService.getRun(RUN_DEFINITIONS.CALIBRATION, runTypeId, detector); + if (runInfo) { + calibrationRunsPerDetector[detector].push(runInfo); + } + } + } + return calibrationRunsPerDetector; + } + + /* + * Private Loaders + */ + /** + * Load calibration mapping for each detector as per the KV store + * @return {Promise} - map of calibration configuration + * + * @example + * { "XYZ": ["CALIB1", "CALIB2"], "ABC": ["XCALIB"] } + */ + async _retrieveCalibrationForDetector() { try { - /** - * @type {Object>} - * @example - * { "XYZ": ["CALIB1", "CALIB2"], "ABC": ["XCALIB"] } - */ - this._calibrationPerDetectorMap = await this._apricotService.getRuntimeEntryByComponent(COG, CALIBRATION_MAPPING); + const calibrationMappings = await this._apricotService.getRuntimeEntryByComponent(COG, CALIBRATION_MAPPING); + return JSON.parse(calibrationMappings); } catch (error) { const err = grpcErrorToNativeError(error); this._logger.errorMessage(`Unable to load calibration mapping due to: ${err}`, {level: LOG_LEVEL.OPERATIONS, system: 'GUI', facility: 'calibration-service'} ) - this._calibrationPerDetectorMap = {}; } + return {}; } /** @@ -100,4 +132,4 @@ class CalibrationRunService { } } -module.exports = {CalibrationRunService}; +module.exports = {RunService}; diff --git a/Control/public/environment/Environment.js b/Control/public/environment/Environment.js index 9b5624bd7..a3c1f58b5 100644 --- a/Control/public/environment/Environment.js +++ b/Control/public/environment/Environment.js @@ -163,7 +163,6 @@ export default class Environment extends Observable { this.itemNew = RemoteData.loading(); this.notify(); - console.log(itemForm) const {result, ok} = await this.model.loader.post(`/api/core/request`, itemForm); this.itemNew = !ok ? RemoteData.failure(result.message) : RemoteData.notAsked(); this.model.router.go(`?page=environments`); diff --git a/Control/test/lib/controllers/mocha-environment.controller.js b/Control/test/lib/controllers/mocha-environment.controller.js index a13e39379..8e2f9f838 100644 --- a/Control/test/lib/controllers/mocha-environment.controller.js +++ b/Control/test/lib/controllers/mocha-environment.controller.js @@ -53,7 +53,7 @@ describe('EnvironmentController test suite', () => { it('should respond with error if service for retrieving information failed', async () => { await envCtrl.getEnvironment({params: {id: ENVIRONMENT_ID_FAILED_TO_RETRIEVE}}, res); - assert.ok(res.status.calledWith(502)); + assert.ok(res.status.calledWith(500)); assert.ok(res.json.calledWith({message: `Data service failed`})); }); diff --git a/Control/test/lib/controllers/mocha-run-controller.test.js b/Control/test/lib/controllers/mocha-run-controller.test.js new file mode 100644 index 000000000..292585944 --- /dev/null +++ b/Control/test/lib/controllers/mocha-run-controller.test.js @@ -0,0 +1,53 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * 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. +*/ +/* eslint-disable max-len */ + +const assert = require('assert'); +const sinon = require('sinon'); + +const {RunController} = require('../../../lib/controllers/Run.controller.js'); + +describe(`'RunController' test suite`, () => { + const res = { + status: sinon.stub().returnsThis(), + json: sinon.stub() + } + + describe(`'getCalibrationRunsHandler' test suite`, async () => { + it('should successfully return calibrations runs grouped by detector', async () => { + const runs = { + TPC: [ + {runNumber: 1}, + {runNumber: 2}, + ] + }; + const runController = new RunController({ + retrieveCalibrationRunsGroupedByDetector: sinon.stub().resolves(runs) + }); + await runController.getCalibrationRunsHandler({}, res); + assert.ok(res.status.calledWith(200)); + assert.ok(res.json.calledWith(runs)); + }); + + it('should return 500 response as there was a problem internally', async () => { + const runController = new RunController({ + retrieveCalibrationRunsGroupedByDetector: sinon.stub().rejects(new Error('Something went wrong')) + }); + await runController.getCalibrationRunsHandler({}, res); + assert.ok(res.status.calledWith(500)); + assert.ok(res.json.calledWith({message: 'Something went wrong'})); + }); + }); + +}); diff --git a/Control/test/lib/controllers/mocha-workflowTemplate.controller.test.js b/Control/test/lib/controllers/mocha-workflowTemplate.controller.test.js index 6baa7a2ff..e4fe440f6 100644 --- a/Control/test/lib/controllers/mocha-workflowTemplate.controller.test.js +++ b/Control/test/lib/controllers/mocha-workflowTemplate.controller.test.js @@ -78,7 +78,7 @@ describe('WorkflowController test suite', () => { retrieveWorkflowMappings: sinon.stub().rejects(new Error('No mappings found')) }); await workflowCtrl.getWorkflowMapping({}, res); - assert.ok(res.status.calledWith(502)); + assert.ok(res.status.calledWith(500)); assert.ok(res.json.calledWith({message: 'No mappings found'})); }); }); diff --git a/Control/test/lib/services/mocha-bookkeeping.service.test.js b/Control/test/lib/services/mocha-bookkeeping.service.test.js index f6aad6d5d..304c88ad8 100644 --- a/Control/test/lib/services/mocha-bookkeeping.service.test.js +++ b/Control/test/lib/services/mocha-bookkeeping.service.test.js @@ -105,14 +105,14 @@ describe('BookkeepingService test suite', () => { assert.deepStrictEqual(run, runInfo); }); - it('should successfully return an empty run if none was found', async () => { + it('should successfully return null run if none was found', async () => { const run = await bkp.getRun('CALIBRATION', 2, 'TPC'); - assert.deepStrictEqual(run, {}); + assert.deepStrictEqual(run, null); }); - it('should successfully return an empty run even if bkp service throws error', async () => { + it('should successfully return null run even if bkp service throws error', async () => { const run = await bkp.getRun('CALIBRATION', 3, 'TPC'); - assert.deepStrictEqual(run, {}); + assert.deepStrictEqual(run, null); }); }); }); diff --git a/Control/test/lib/services/mocha-calibration-run-service.test.js b/Control/test/lib/services/mocha-calibration-run-service.test.js deleted file mode 100644 index 3fad11197..000000000 --- a/Control/test/lib/services/mocha-calibration-run-service.test.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * 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. -*/ -/* eslint-disable max-len */ - -const assert = require('assert'); -const sinon = require('sinon'); - -const {RUNTIME_COMPONENT: {COG}, RUNTIME_KEY: {CALIBRATION_MAPPING}} = require('./../../../lib/common/kvStore/runtime.enum.js'); -const {CalibrationRunService} = require('./../../../lib/services/CalibrationRun.service.js'); -const {BookkeepingService} = require('../../../lib/services/Bookkeeping.service.js'); -const {NotFoundError} = require('../../../lib/errors/NotFoundError.js'); - -describe('CalibrationRunService test suite', () => { - const url = 'http://bkp-test.cern.ch:8888'; - const bkpService = new BookkeepingService({url, token: ''}); - bkpService.getRunTypes = sinon.stub().resolves({}); - - - - describe(`'init' test suite`, async () => { - it('should retrieve a map of of runTypes and calibration mappings and save them in-memory', async () => { - const getRuntimeEntryByComponent = sinon.stub(); - getRuntimeEntryByComponent.withArgs(COG, CALIBRATION_MAPPING).resolves({TPC: ['NOISE', 'PULSE'], ABC: ['SOME-OTHER']}); - const apricotServiceStub = {getRuntimeEntryByComponent}; - const calibrationService = new CalibrationRunService(bkpService, apricotServiceStub); - - await calibrationService.init(); - - assert.deepStrictEqual(calibrationService.runTypes, {}); - assert.deepStrictEqual(calibrationService.calibrationPerDetectorMap, {TPC: ['NOISE', 'PULSE'], ABC: ['SOME-OTHER']}); - }); - - it('should keep an empty object for mappings due to failure to retrieve data from apricot', async () => { - const getRuntimeEntryByComponent = sinon.stub(); - getRuntimeEntryByComponent.withArgs(COG, CALIBRATION_MAPPING).rejects(new NotFoundError('key not found')); - const apricotServiceStub = {getRuntimeEntryByComponent}; - const calibrationService = new CalibrationRunService(bkpService, apricotServiceStub); - await calibrationService.init(); - - assert.deepStrictEqual(calibrationService.calibrationPerDetectorMap, {}); - }); - }); -}); diff --git a/Control/test/lib/services/mocha-run-service.test.js b/Control/test/lib/services/mocha-run-service.test.js new file mode 100644 index 000000000..f8cd7eddb --- /dev/null +++ b/Control/test/lib/services/mocha-run-service.test.js @@ -0,0 +1,93 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * 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. +*/ +/* eslint-disable max-len */ + +const assert = require('assert'); +const sinon = require('sinon'); + +const {RUN_DEFINITIONS} = 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(`'_retrieveCalibrationForDetector test suite`, async () => { + it('should return empty object if apricot service throws error', async () => { + const getRuntimeEntryByComponent = sinon.stub().rejects(new NotFoundError('key not found')); + const runSrv = new RunService({}, {getRuntimeEntryByComponent}); + + const result = await runSrv._retrieveCalibrationForDetector(); + assert.deepStrictEqual(result, {}); + }); + + it('should return empty object if JSON.parse of response throws error', async () => { + const getRuntimeEntryByComponent = sinon.stub().resolves('{"prop": "Invalid Object}'); + const runSrv = new RunService({}, {getRuntimeEntryByComponent}); + + const result = await runSrv._retrieveCalibrationForDetector(); + assert.deepStrictEqual(result, {}); + }); + + it('should successfully return results', async () => { + const getRuntimeEntryByComponent = sinon.stub().resolves('{"TPC": ["NOISE", "PULSE"], "ABC": ["SOME-OTHER"]}'); + const runSrv = new RunService({}, {getRuntimeEntryByComponent}); + + const result = await runSrv._retrieveCalibrationForDetector(); + + assert.deepStrictEqual(result, {TPC: ['NOISE', 'PULSE'], ABC: ['SOME-OTHER']}); + }); + }); + + describe(`'retrieveCalibrationRunsGroupedByDetector test suite`, async () => { + it('should return an empty object due to missing calibrationsPerDetectorMap being empty', async () => { + const runSrv = new RunService(); + const result = await runSrv.retrieveCalibrationRunsGroupedByDetector(); + assert.deepStrictEqual(result, {}); + }); + + it('should return an object with calibration runs grouped by detector', async () => { + const getRun = sinon.stub(); + getRun.withArgs(RUN_DEFINITIONS.CALIBRATION, 0, 'TPC').resolves({runNumber: 1}); + getRun.withArgs(RUN_DEFINITIONS.CALIBRATION, 1, 'TPC').resolves({runNumber: 2}); + getRun.withArgs(RUN_DEFINITIONS.CALIBRATION, 2, 'ABC').resolves({runNumber: 3}); + getRun.withArgs(RUN_DEFINITIONS.CALIBRATION, 1, 'ABC').resolves({runNumber: 4}); + getRun.withArgs(RUN_DEFINITIONS.CALIBRATION, 1, 'XYZ').resolves(undefined); + + const runSrv = new RunService({getRun}, {}); + runSrv._runTypes = { + NOISE: 0, + PULSE: 1, + SOMEOTHER: 2, + }; + runSrv._calibrationPerDetectorMap = { + TPC: ['NOISE', 'PULSE'], + ABC: ['SOMEOTHER', 'PULSE'], + XYZ: ['NONEXISTENT', 'PULSE'], // detector with no run found or nonexistent type + } + const result = await runSrv.retrieveCalibrationRunsGroupedByDetector(); + assert.deepStrictEqual(result, { + TPC: [ + {runNumber: 1}, + {runNumber: 2}, + ], + ABC: [ + {runNumber: 3}, + {runNumber: 4}, + ], + XYZ: [] + }); + }); + }); +}); diff --git a/Control/test/test-config.js b/Control/test/test-config.js index adc3f70d3..6c78ca5b1 100644 --- a/Control/test/test-config.js +++ b/Control/test/test-config.js @@ -58,6 +58,9 @@ module.exports = { bookkeepingGui: { url: 'http://localhost:2021' }, + bookkeeping: { + url: 'http://localhost:2021', + }, qcGui: { url: 'http://localhost:2022' },