diff --git a/lib/server/controllers/qcFlag.controller.js b/lib/server/controllers/qcFlag.controller.js index f5de50dbba..db111cf688 100644 --- a/lib/server/controllers/qcFlag.controller.js +++ b/lib/server/controllers/qcFlag.controller.js @@ -82,7 +82,61 @@ const listQcFlagsHandler = async (req, res) => { } }; +// eslint-disable-next-line valid-jsdoc +/** + * Create QcFlag + */ +const createQcFlagHandler = async (req, res) => { + const validatedDTO = await dtoValidator( + DtoFactory.bodyOnly(Joi.object({ + from: Joi.number().required().allow(null), + to: Joi.number().required().allow(null), + comment: Joi.string().optional().allow(null), + flagTypeId: Joi.number().required(), + runNumber: Joi.number().required(), + dplDetectorId: Joi.number().required(), + dataPassId: Joi.number(), + simulationPassId: Joi.number(), + }).xor('dataPassId', 'simulationPassId')), + req, + res, + ); + if (validatedDTO) { + try { + const { + from, + to, + comment, + flagTypeId, + runNumber, + dplDetectorId, + dataPassId, + simulationPassId, + } = validatedDTO.body; + const parameters = { from, to, comment }; + const relations = { + user: { externalUserId: validatedDTO.session.externalId }, + flagTypeId, + runNumber, + dplDetectorId, + dataPassId, + simulationPassId, + }; + let createdFlag; + if (dataPassId) { + createdFlag = await qcFlagService.createForDataPass(parameters, relations); + } else { + createdFlag = await qcFlagService.createForSimulationPass(parameters, relations); + } + res.status(201).json({ data: createdFlag }); + } catch (error) { + updateExpressResponseFromNativeError(res, error); + } + } +}; + exports.QcFlagController = { getQcFlagByIdHandler, listQcFlagsHandler, + createQcFlagHandler, }; diff --git a/lib/server/routers/qcFlag.router.js b/lib/server/routers/qcFlag.router.js index e08f01dffe..df43280dcb 100644 --- a/lib/server/routers/qcFlag.router.js +++ b/lib/server/routers/qcFlag.router.js @@ -25,5 +25,9 @@ exports.qcFlagsRouter = { method: 'get', controller: QcFlagController.listQcFlagsHandler, }, + { + method: 'post', + controller: QcFlagController.createQcFlagHandler, + }, ], }; diff --git a/lib/server/services/qualityControlFlag/QcFlagService.js b/lib/server/services/qualityControlFlag/QcFlagService.js index be3bc4a252..dbd3a71091 100644 --- a/lib/server/services/qualityControlFlag/QcFlagService.js +++ b/lib/server/services/qualityControlFlag/QcFlagService.js @@ -11,11 +11,14 @@ * or submit itself to any jurisdiction. */ -const { repositories: { QcFlagRepository } } = require('../../../database/index.js'); +const { repositories: { QcFlagRepository, DplDetectorRepository, RunRepository } } = require('../../../database/index.js'); const { dataSource } = require('../../../database/DataSource.js'); const { qcFlagAdapter } = require('../../../database/adapters/index.js'); const { BadParameterError } = require('../../errors/BadParameterError.js'); const { NotFoundError } = require('../../errors/NotFoundError.js'); +const { getUserOrFail } = require('../user/getUserOrFail.js'); + +const NON_QC_DETECTORS = new Set(['TST']); /** * Quality control flags service @@ -53,6 +56,211 @@ class QcFlagService { return qcFlag; } + /** + * Validate QC flag timestamps + * If null timestamp was provided, given timestamp is replaced by run's startTime or endTime + * @param {{from: number, to: number}} timestamps QC flag timestamps + * @param {Run} targetRun run which for QC flag is to be set + * @throws {BadParameterError} + * @return {{fromTime: number, toTime: number}} prepared timestamps + */ + _prepareQcFlagPeriod({ from, to }, targetRun) { + const fromTime = from ?? targetRun.startTime; + const toTime = to ?? targetRun.endTime; + + if (!fromTime || !toTime) { + if (!fromTime && !toTime) { + return { fromTime: null, toTime: null }; + } else { + throw new BadParameterError('Only null QC flag timestamps are accepted as run.startTime or run.endTime is missing'); + } + } + + if (fromTime >= toTime) { + throw new BadParameterError('Parameter "to" timestamp must be greater than "from" timestamp'); + } + + if (fromTime < targetRun.startTime || targetRun.endTime < toTime) { + // eslint-disable-next-line max-len + throw new BadParameterError(`Given QC flag period (${fromTime}, ${toTime}) is out of run (${targetRun.startTime}, ${targetRun.endTime}) period`); + } + return { fromTime, toTime }; + } + + /** + * Validate QC flag DPL detector + * @param {number} dplDetectorId DPL detector + * @throws {BadParameterError} + * @return {void} + */ + async _validateQcFlagDplDetector(dplDetectorId) { + const dplDetector = await DplDetectorRepository.findOne({ where: { id: dplDetectorId } }); + if (!dplDetector?.name || NON_QC_DETECTORS.has(dplDetector.name)) { + throw new BadParameterError(`Invalid DPL detector (${dplDetector.name})`); + } + return dplDetector; + } + + /** + * Create new instance of quality control flags for data pass + * @param {Partial} parameters flag instance parameters + * @param {object} [relations] QC Flag Type entity relations + * @param {Partial} [relations.user] user identifiers + * @param {number} [parameters.flagTypeId] flag type id + * @param {number} [parameters.runNumber] associated run's number + * @param {number} [parameters.dataPassId] associated dataPass' id + * @param {number} [parameters.dplDetectorId] associated dplDetector's id + * @return {Promise} promise + * @throws {BadParameterError, NotFoundError} + */ + async createForDataPass(parameters, relations = {}) { + const { + from = null, + to = null, + comment, + } = parameters; + const { + user: { userId, externalUserId } = {}, + flagTypeId, + runNumber, + dataPassId, + dplDetectorId, + } = relations; + + return dataSource.transaction(async () => { + // Check user + const user = await getUserOrFail({ userId, externalUserId }); + + // Check associations + const dplDetector = await this._validateQcFlagDplDetector(dplDetectorId); + + const targetRun = await RunRepository.findOne({ + subQuery: false, + attributes: ['timeTrgStart', 'timeTrgEnd'], + where: { runNumber }, + include: [ + { + association: 'dataPass', + where: { id: dataPassId }, + through: { attributes: [] }, + attributes: ['id'], + required: true, + }, + { + association: 'detectors', + where: { name: dplDetector.name }, + through: { attributes: [] }, + attributes: [], + required: true, + }, + ], + }); + if (!targetRun) { + // eslint-disable-next-line max-len + throw new BadParameterError(`There is not association between data pass with this id (${dataPassId}), run with this number (${runNumber}) and detector with this name (${dplDetector.name})`); + } + + const { fromTime, toTime } = this._prepareQcFlagPeriod({ from, to }, targetRun); + + // Insert + const newInstance = await QcFlagRepository.insert({ + from: fromTime, + to: toTime, + comment, + createdById: user.id, + flagTypeId, + runNumber, + dplDetectorId, + }); + + const createdFlag = await QcFlagRepository.findOne(this.prepareQueryBuilder().where('id').is(newInstance.id)); + + await createdFlag.addDataPasses(targetRun.dataPass); + + return qcFlagAdapter.toEntity(createdFlag); + }); + } + + /** + * Create new instance of quality control flags for simulation pass + * @param {Partial} parameters flag instance parameters + * @param {object} [relations] QC Flag Type entity relations + * @param {Partial} [relations.user] user identifiers + * @param {number} [parameters.flagTypeId] flag type id + * @param {number} [parameters.runNumber] associated run's number + * @param {number} [parameters.simulationPassId] associated simulationPass' id + * @param {number} [parameters.dplDetectorId] associated dplDetector's id + * @return {Promise} promise + * @throws {BadParameterError, NotFoundError} + */ + async createForSimulationPass(parameters, relations = {}) { + const { + from = null, + to = null, + comment, + } = parameters; + const { + user: { userId, externalUserId } = {}, + flagTypeId, + runNumber, + simulationPassId, + dplDetectorId, + } = relations; + + return dataSource.transaction(async () => { + // Check user + const user = await getUserOrFail({ userId, externalUserId }); + + // Check associations + const dplDetector = await this._validateQcFlagDplDetector(dplDetectorId); + + const targetRun = await RunRepository.findOne({ + subQuery: false, + attributes: ['timeTrgStart', 'timeTrgEnd'], + where: { runNumber }, + include: [ + { + association: 'simulationPasses', + where: { id: simulationPassId }, + through: { attributes: [] }, + attributes: ['id'], + required: true, + }, + { + association: 'detectors', + where: { name: dplDetector.name }, + through: { attributes: [] }, + attributes: [], + required: true, + }, + ], + }); + if (!targetRun) { + // eslint-disable-next-line max-len + throw new BadParameterError(`There is not association between simulation pass with this id (${simulationPassId}), run with this number (${runNumber}) and detector with this name (${dplDetector.name})`); + } + + const { fromTime, toTime } = this._prepareQcFlagPeriod({ from, to }, targetRun); + + // Insert + const newInstance = await QcFlagRepository.insert({ + from: fromTime, + to: toTime, + comment, + createdById: user.id, + flagTypeId, + runNumber, + dplDetectorId, + }); + + const createdFlag = await QcFlagRepository.findOne(this.prepareQueryBuilder().where('id').is(newInstance.id)); + + await createdFlag.addSimulationPasses(targetRun.simulationPasses); + + return qcFlagAdapter.toEntity(createdFlag); + }); + } + /** * Get all quality control flags instances * @param {object} [options.filter] filtering defintion diff --git a/test/api/qcFlags.test.js b/test/api/qcFlags.test.js index d97c59ca36..32aa8f8159 100644 --- a/test/api/qcFlags.test.js +++ b/test/api/qcFlags.test.js @@ -10,7 +10,7 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ - +const { repositories: { QcFlagRepository } } = require('../../lib/database'); const { expect } = require('chai'); const request = require('supertest'); const { server } = require('../../lib/application'); @@ -279,4 +279,95 @@ module.exports = () => { expect(titleError.detail).to.equal('"query.page.limit" must be greater than or equal to 1'); }); }); + + describe('POST /api/qcFlags', () => { + it('should successfuly create QC flag instance for data pass', async () => { + const qcFlagCreationParameters = { + from: new Date('2019-08-09 01:29:50').getTime(), + to: new Date('2019-08-09 05:40:00').getTime(), + comment: 'VERY INTERESTING REMARK', + flagTypeId: 2, + runNumber: 106, + dataPassId: 1, + dplDetectorId: 1, + }; + + const response = await request(server).post('/api/qcFlags').send(qcFlagCreationParameters); + expect(response.status).to.be.equal(201); + const { data: createdQcFlag } = response.body; + const { dataPassId, ...expectedProperties } = qcFlagCreationParameters; + { + const { from, to, comment, flagTypeId, runNumber, dplDetectorId } = createdQcFlag; + expect({ from, to, comment, flagTypeId, runNumber, dplDetectorId }).to.be.eql(expectedProperties); + } + { + const { from, to, comment, flagTypeId, runNumber, dplDetectorId, dataPasses } = await QcFlagRepository.findOne({ + include: [{ association: 'dataPasses' }], + where: { + id: createdQcFlag.id, + }, + }); + expect({ from: from.getTime(), to: to.getTime(), comment, flagTypeId, runNumber, dplDetectorId }).to.be.eql(expectedProperties); + expect(dataPasses.map(({ id }) => id)).to.have.all.members([dataPassId]); + } + }); + + it('should successfuly create QC flag instance for simulation pass', async () => { + const qcFlagCreationParameters = { + from: new Date('2019-08-09 01:29:50').getTime(), + to: new Date('2019-08-09 05:40:00').getTime(), + comment: 'VERY INTERESTING REMARK', + flagTypeId: 2, + runNumber: 106, + simulationPassId: 1, + dplDetectorId: 1, + }; + + const response = await request(server).post('/api/qcFlags').send(qcFlagCreationParameters); + expect(response.status).to.be.equal(201); + const { data: createdQcFlag } = response.body; + const { simulationPassId, ...expectedProperties } = qcFlagCreationParameters; + { + const { from, to, comment, flagTypeId, runNumber, dplDetectorId } = createdQcFlag; + expect({ from, to, comment, flagTypeId, runNumber, dplDetectorId }).to.be.eql(expectedProperties); + } + { + const { from, to, comment, flagTypeId, runNumber, dplDetectorId, simulationPasses } = await QcFlagRepository.findOne({ + include: [{ association: 'simulationPasses' }], + where: { + id: createdQcFlag.id, + }, + }); + expect({ from: from.getTime(), to: to.getTime(), comment, flagTypeId, runNumber, dplDetectorId }).to.be.eql(expectedProperties); + expect(simulationPasses.map(({ id }) => id)).to.have.all.members([simulationPassId]); + } + }); + + it('should fail to create QC flag instance when dataPass and simualtion are both specified', async () => { + const qcFlagCreationParameters = { + from: new Date('2019-08-09 01:29:50').getTime(), + to: new Date('2019-08-09 05:40:00').getTime(), + comment: 'VERY INTERESTING REMARK', + flagTypeId: 2, + runNumber: 106, + simulationPassId: 1, + dataPassId: 1, + dplDetectorId: 1, + }; + + const response = await request(server).post('/api/qcFlags').send(qcFlagCreationParameters); + expect(response.status).to.be.equal(400); + const { errors } = response.body; + expect(errors).to.be.eql([ + { + status: '422', + source: { + pointer: '/data/attributes/body', + }, + title: 'Invalid Attribute', + detail: '"body" contains a conflict between exclusive peers [dataPassId, simulationPassId]', + }, + ]); + }); + }); }; diff --git a/test/lib/server/services/qualityControlFlag/QcFlagService.test.js b/test/lib/server/services/qualityControlFlag/QcFlagService.test.js index 93d8b3e47f..bfe3d0855d 100644 --- a/test/lib/server/services/qualityControlFlag/QcFlagService.test.js +++ b/test/lib/server/services/qualityControlFlag/QcFlagService.test.js @@ -10,7 +10,7 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ - +const { repositories: { QcFlagRepository, RunRepository } } = require('../../../../../lib/database'); const { resetDatabaseContent } = require('../../../../utilities/resetDatabaseContent.js'); const { expect } = require('chai'); const assert = require('assert'); @@ -19,8 +19,8 @@ const { qcFlagService } = require('../../../../../lib/server/services/qualityCon const qcFlagWithId1 = { id: 1, - from: (1565314200 - 10000) * 1000, - to: (1565314200 + 10000) * 1000, + from: new Date('2019-08-08 22:43:20').getTime(), + to: new Date('2019-08-09 04:16:40').getTime(), comment: 'Some qc comment 1', // Associations @@ -28,8 +28,8 @@ const qcFlagWithId1 = { flagTypeId: 11, // LimitedAcceptance runNumber: 106, dplDetectorId: 1, // CPV - createdAt: 1707825436000, - updatedAt: 1707825436000, + createdAt: new Date('2024-02-13 11:57:16').getTime(), + updatedAt: new Date('2024-02-13 11:57:16').getTime(), createdBy: { id: 1, @@ -230,4 +230,352 @@ module.exports = () => { expect(fetchedSortedProperties).to.have.all.ordered.members([...fetchedSortedProperties].sort().reverse()); }); }); + + describe('Creating Quality Control Flag for data pass', () => { + it('should fail to create quality control flag due to incorrect external user id', async () => { + const qcFlagCreationParameters = { + from: new Date('2019-08-09 01:29:50').getTime(), + to: new Date('2019-08-09 05:40:00').getTime(), + comment: 'VERY INTERESTING REMARK', + }; + + const relations = { + user: { + externalUserId: 9999999, // Failing property + }, + flagTypeId: 2, + runNumber: 106, + dataPassId: 1, + dplDetectorId: 1, + }; + + await assert.rejects( + () => qcFlagService.createForDataPass(qcFlagCreationParameters, relations), + new BadParameterError('User with this external id (9999999) could not be found'), + ); + }); + + it('should fail to create quality control flag because qc flag `from` timestamp is smaller than run.startTime', async () => { + const qcFlagCreationParameters = { + from: new Date('2019-08-08 11:36:40').getTime(), // Failing property + to: new Date('2019-08-09 05:40:00').getTime(), + comment: 'VERY INTERESTING REMARK', + }; + + const relations = { + user: { + externalUserId: 456, + }, + flagTypeId: 2, + runNumber: 106, + dataPassId: 1, + dplDetectorId: 1, + }; + + await assert.rejects( + () => qcFlagService.createForDataPass(qcFlagCreationParameters, relations), + // eslint-disable-next-line max-len + new BadParameterError(`Given QC flag period (${new Date('2019-08-08 11:36:40').getTime()}, ${new Date('2019-08-09 05:40:00').getTime()}) is out of run (${new Date('2019-08-08 13:00:00').getTime()}, ${new Date('2019-08-09 14:00:00').getTime()}) period`), + ); + }); + + it('should fail to create quality control flag because qc flag `from` timestamp is greater than `to` timestamp', async () => { + const qcFlagCreationParameters = { + from: new Date('2019-08-09 04:16:40').getTime(), // Failing property + to: new Date('2019-08-08 21:20:00').getTime(), // Failing property + comment: 'VERY INTERESTING REMARK', + }; + + const relations = { + user: { + externalUserId: 456, + }, + flagTypeId: 2, + runNumber: 106, + dataPassId: 1, + dplDetectorId: 1, + }; + + await assert.rejects( + () => qcFlagService.createForDataPass(qcFlagCreationParameters, relations), + new BadParameterError('Parameter "to" timestamp must be greater than "from" timestamp'), + ); + }); + + it('should fail to create QC flag because there is no association between data pass, run and dpl detector', async () => { + const qcFlagCreationParameters = { + from: new Date('2019-08-09 01:29:50').getTime(), + to: new Date('2019-08-09 05:40:00').getTime(), + comment: 'VERY INTERESTING REMARK', + }; + + const relations = { + user: { + externalUserId: 456, + }, + flagTypeId: 2, + runNumber: 106, + dataPassId: 9999, // Failing property + dplDetectorId: 1, + }; + + await assert.rejects( + () => qcFlagService.createForDataPass(qcFlagCreationParameters, relations), + // eslint-disable-next-line max-len + new BadParameterError('There is not association between data pass with this id (9999), run with this number (106) and detector with this name (CPV)'), + ); + }); + + it('should succesfuly create quality control flag with externalUserId', async () => { + const qcFlagCreationParameters = { + from: new Date('2019-08-09 01:29:50').getTime(), + to: new Date('2019-08-09 05:40:00').getTime(), + comment: 'VERY INTERESTING REMARK', + }; + + const relations = { + user: { + externalUserId: 456, + }, + flagTypeId: 2, + runNumber: 106, + dataPassId: 1, + dplDetectorId: 1, + }; + + const { id, from, to, comment, flagTypeId, runNumber, dplDetectorId, createdBy: { externalId: externalUserId } } = + await qcFlagService.createForDataPass(qcFlagCreationParameters, relations); + + expect({ from, to, comment, flagTypeId, runNumber, dplDetectorId, externalUserId }).to.be.eql({ + from: qcFlagCreationParameters.from, + to: qcFlagCreationParameters.to, + comment: qcFlagCreationParameters.comment, + flagTypeId: relations.flagTypeId, + runNumber: relations.runNumber, + dplDetectorId: relations.dplDetectorId, + externalUserId: relations.user.externalUserId, + }); + + const fetchedFlagWithDataPass = await QcFlagRepository.findOne({ + include: [{ association: 'dataPasses' }], + where: { + id, + }, + }); + expect(fetchedFlagWithDataPass.dataPasses.map(({ id }) => id)).to.have.all.members([relations.dataPassId]); + }); + + it('should succesfuly create quality control flag without timstamps', async () => { + const qcFlagCreationParameters = { + comment: 'VERY INTERESTING REMARK', + }; + + const relations = { + user: { + externalUserId: 456, + }, + flagTypeId: 2, + runNumber: 106, + dataPassId: 1, + dplDetectorId: 1, + }; + + const { id, from, to, comment, flagTypeId, runNumber, dplDetectorId, createdBy: { externalId: externalUserId } } = + await qcFlagService.createForDataPass(qcFlagCreationParameters, relations); + + const { startTime, endTime } = await RunRepository.findOne({ where: { runNumber } }); + + expect({ from, to, comment, flagTypeId, runNumber, dplDetectorId, externalUserId }).to.be.eql({ + from: startTime, + to: endTime, + comment: qcFlagCreationParameters.comment, + flagTypeId: relations.flagTypeId, + runNumber: relations.runNumber, + dplDetectorId: relations.dplDetectorId, + externalUserId: relations.user.externalUserId, + }); + + const fetchedFlagWithDataPass = await QcFlagRepository.findOne({ + include: [{ association: 'dataPasses' }], + where: { + id, + }, + }); + expect(fetchedFlagWithDataPass.dataPasses.map(({ id }) => id)).to.have.all.members([relations.dataPassId]); + }); + }); + + describe('Creating Quality Control Flag for simulation pass', () => { + it('should fail to create quality control flag due to incorrect external user id', async () => { + const qcFlagCreationParameters = { + from: new Date('2019-08-09 01:29:50').getTime(), + to: new Date('2019-08-09 05:40:00').getTime(), + comment: 'VERY INTERESTING REMARK', + }; + + const relations = { + user: { + externalUserId: 9999999, + }, + flagTypeId: 2, + runNumber: 106, + simulationPassId: 1, + dplDetectorId: 1, + }; + + await assert.rejects( + () => qcFlagService.createForSimulationPass(qcFlagCreationParameters, relations), + new BadParameterError('User with this external id (9999999) could not be found'), + ); + }); + + it('should fail to create quality control flag because qc flag `from` timestamp is smaller than run.startTime', async () => { + const qcFlagCreationParameters = { + from: new Date('2019-08-08 11:36:40').getTime(), // Failing property + to: new Date('2019-08-09 05:40:00').getTime(), + comment: 'VERY INTERESTING REMARK', + }; + + const relations = { + user: { + externalUserId: 456, + }, + flagTypeId: 2, + runNumber: 106, + simulationPassId: 1, + dplDetectorId: 1, + }; + + await assert.rejects( + () => qcFlagService.createForSimulationPass(qcFlagCreationParameters, relations), + // eslint-disable-next-line max-len + new BadParameterError(`Given QC flag period (${new Date('2019-08-08 11:36:40').getTime()}, ${new Date('2019-08-09 05:40:00').getTime()}) is out of run (${new Date('2019-08-08 13:00:00').getTime()}, ${new Date('2019-08-09 14:00:00').getTime()}) period`), + ); + }); + + it('should fail to create quality control flag because qc flag `from` timestamp is greater than `to` timestamp', async () => { + const qcFlagCreationParameters = { + from: new Date('2019-08-09 04:16:40').getTime(), // Failing property + to: new Date('2019-08-08 21:20:00').getTime(), // Failing property + comment: 'VERY INTERESTING REMARK', + }; + + const relations = { + user: { + externalUserId: 456, + }, + flagTypeId: 2, + runNumber: 106, + simulationPassId: 1, + dplDetectorId: 1, + }; + + await assert.rejects( + () => qcFlagService.createForSimulationPass(qcFlagCreationParameters, relations), + new BadParameterError('Parameter "to" timestamp must be greater than "from" timestamp'), + ); + }); + + it('should fail to create QC flag because there is no association between simulation pass, run and dpl detector', async () => { + const qcFlagCreationParameters = { + from: new Date('2019-08-09 01:29:50').getTime(), + to: new Date('2019-08-09 05:40:00').getTime(), + comment: 'VERY INTERESTING REMARK', + }; + + const relations = { + user: { + externalUserId: 456, + }, + flagTypeId: 2, + runNumber: 106, + simulationPassId: 9999, // Failing property + dplDetectorId: 1, + }; + + await assert.rejects( + () => qcFlagService.createForSimulationPass(qcFlagCreationParameters, relations), + // eslint-disable-next-line max-len + new BadParameterError('There is not association between simulation pass with this id (9999), run with this number (106) and detector with this name (CPV)'), + ); + }); + + it('should succesfuly create quality control flag with externalUserId', async () => { + const qcFlagCreationParameters = { + from: new Date('2019-08-09 01:29:50').getTime(), + to: new Date('2019-08-09 05:40:00').getTime(), + comment: 'VERY INTERESTING REMARK', + }; + + const relations = { + user: { + externalUserId: 456, + }, + flagTypeId: 2, + runNumber: 106, + simulationPassId: 1, + dplDetectorId: 1, + }; + + const { id, from, to, comment, flagTypeId, runNumber, dplDetectorId, createdBy: { externalId: externalUserId } } = + await qcFlagService.createForSimulationPass(qcFlagCreationParameters, relations); + + expect({ from, to, comment, flagTypeId, runNumber, dplDetectorId, externalUserId }).to.be.eql({ + from: qcFlagCreationParameters.from, + to: qcFlagCreationParameters.to, + comment: qcFlagCreationParameters.comment, + flagTypeId: relations.flagTypeId, + runNumber: relations.runNumber, + dplDetectorId: relations.dplDetectorId, + externalUserId: relations.user.externalUserId, + }); + + const fetchedFlagWithSimulationPass = await QcFlagRepository.findOne({ + include: [{ association: 'simulationPasses' }], + where: { + id, + }, + }); + expect(fetchedFlagWithSimulationPass.simulationPasses.map(({ id }) => id)).to.have.all.members([relations.simulationPassId]); + }); + + it('should succesfuly create quality control flag without timstamps', async () => { + const qcFlagCreationParameters = { + comment: 'VERY INTERESTING REMARK', + }; + + const relations = { + user: { + externalUserId: 456, + }, + flagTypeId: 2, + runNumber: 106, + simulationPassId: 1, + dplDetectorId: 1, + }; + + const { id, from, to, comment, flagTypeId, runNumber, dplDetectorId, createdBy: { externalId: externalUserId } } = + await qcFlagService.createForSimulationPass(qcFlagCreationParameters, relations); + + const { startTime, endTime } = await RunRepository.findOne({ where: { runNumber } }); + + expect({ from, to, comment, flagTypeId, runNumber, dplDetectorId, externalUserId }).to.be.eql({ + from: startTime, + to: endTime, + comment: qcFlagCreationParameters.comment, + flagTypeId: relations.flagTypeId, + runNumber: relations.runNumber, + dplDetectorId: relations.dplDetectorId, + externalUserId: relations.user.externalUserId, + }); + + const fetchedFlagWithSimulationPass = await QcFlagRepository.findOne({ + include: [{ association: 'simulationPasses' }], + where: { + id, + }, + }); + expect(fetchedFlagWithSimulationPass.simulationPasses.map(({ id }) => id)).to.have.all.members([relations.simulationPassId]); + }); + }); };