diff --git a/lib/domain/dtos/filters/RunFilterDto.js b/lib/domain/dtos/filters/RunFilterDto.js index 2dc9ce98ad..93f3d5e238 100644 --- a/lib/domain/dtos/filters/RunFilterDto.js +++ b/lib/domain/dtos/filters/RunFilterDto.js @@ -32,6 +32,13 @@ const EorReasonFilterDto = Joi.object({ }); exports.RunFilterDto = Joi.object({ + beamModes: CustomJoi.stringArray().items(Joi.string() + .trim() + .pattern(/^[A-Z]+(?: [A-Z]+)*$/) + .messages({ + 'string.pattern.base': + 'Beam modes "{{#value}}" must contain only uppercase letters and single spaces between words.', + })), runNumbers: Joi.string().trim().custom(validateRange).messages({ [RANGE_INVALID]: '{{#message}}', 'string.base': 'Run numbers must be comma-separated numbers or ranges (e.g. 12,15-18)', diff --git a/lib/usecases/run/GetAllRunsUseCase.js b/lib/usecases/run/GetAllRunsUseCase.js index d25762ad00..df1b5f7f5b 100644 --- a/lib/usecases/run/GetAllRunsUseCase.js +++ b/lib/usecases/run/GetAllRunsUseCase.js @@ -82,6 +82,7 @@ class GetAllRunsUseCase { inelasticInteractionRateAtEnd, gaq, detectorsQc, + beamModes, } = filter; if (runNumbers) { @@ -113,6 +114,10 @@ class GetAllRunsUseCase { filteringQueryBuilder.where('definition').oneOf(...definitions); } + if (beamModes) { + filteringQueryBuilder.where('lhcBeamMode').oneOf(...beamModes); + } + if (eorReason) { const eorReasonTypeWhere = {}; if (eorReason.category) { diff --git a/test/api/runs.test.js b/test/api/runs.test.js index 4322040bb3..083771bf02 100644 --- a/test/api/runs.test.js +++ b/test/api/runs.test.js @@ -142,6 +142,37 @@ module.exports = () => { expect(runs).to.lengthOf(20); }); + it('should successfully filter with single beamMode', async () => { + const response = await request(server).get('/api/runs?filter[beamModes]=STABLE BEAMS'); + + expect(response.status).to.equal(200); + const { data: runs } = response.body; + + expect(runs).to.lengthOf(5); + }); + + it('should successfully filter with multiple beamModes', async () => { + const response = await request(server).get('/api/runs?filter[beamModes]=STABLE BEAMS,NO BEAM'); + + expect(response.status).to.equal(200); + const { data: runs } = response.body; + + expect(runs).to.lengthOf(6); + }); + + it('should return 400 if beamModes filter has the incorrect format', async () => { + const beamModeString = '*THERE\'S NON LETTERS IN HERE'; + const response = await request(server).get(`/api/runs?filter[beamModes]=${beamModeString}`); + + + expect(response.status).to.equal(400); + + const { errors: [error] } = response.body; + + expect(error.title).to.equal('Invalid Attribute'); + expect(error.detail).to.equal(`Beam modes "${beamModeString}" must contain only uppercase letters and single spaces between words.`); + }); + it('should successfully filter on multiple specified run numbers', async () => { const response = await request(server).get('/api/runs?filter[runNumbers]=17,18'); diff --git a/test/lib/usecases/run/GetAllRunsUseCase.test.js b/test/lib/usecases/run/GetAllRunsUseCase.test.js index 55e5551485..5b080d056c 100644 --- a/test/lib/usecases/run/GetAllRunsUseCase.test.js +++ b/test/lib/usecases/run/GetAllRunsUseCase.test.js @@ -183,6 +183,32 @@ module.exports = () => { } }); + it('should successfully filter on beamModes', async () => { + const singleBeamMode = ['STABLE BEAMS']; + const multipleBeamMode = ['STABLE BEAMS', 'NO BEAM']; + const nonExistentBeamMode = ['DOES NOT EXIST']; + + getAllRunsDto.query = { filter: { beamModes: singleBeamMode } }; + { + const { runs } = await new GetAllRunsUseCase().execute(getAllRunsDto); + expect(runs).to.have.lengthOf(5); + expect(runs.every(({ lhcBeamMode }) => singleBeamMode.includes(lhcBeamMode))).to.be.true; + } + + getAllRunsDto.query = { filter: { beamModes: multipleBeamMode } }; + { + const { runs } = await new GetAllRunsUseCase().execute(getAllRunsDto); + expect(runs).to.have.lengthOf(6); + expect(runs.every(({ lhcBeamMode }) => multipleBeamMode.includes(lhcBeamMode))).to.be.true; + } + + getAllRunsDto.query = { filter: { beamModes: nonExistentBeamMode } }; + { + const { runs } = await new GetAllRunsUseCase().execute(getAllRunsDto); + expect(runs).to.have.lengthOf(0); + } + }); + it('should successfully filter on run definition', async () => { const PHYSICS_COUNT = 7; const COSMICS_COUNT = 2;