Skip to content

Commit b2d5f62

Browse files
committed
feat: added GET /api/tags/{tagId} endpoint
1 parent 784c17d commit b2d5f62

10 files changed

Lines changed: 291 additions & 14 deletions

File tree

lib/domain/dtos/GetTagDto.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* @license
3+
* Copyright CERN and copyright holders of ALICE O2. This software is
4+
* distributed under the terms of the GNU General Public License v3 (GPL
5+
* Version 3), copied verbatim in the file "COPYING".
6+
*
7+
* See http://alice-o2.web.cern.ch/license for full licensing information.
8+
*
9+
* In applying this license CERN does not waive the privileges and immunities
10+
* granted to it by virtue of its status as an Intergovernmental Organization
11+
* or submit itself to any jurisdiction.
12+
*/
13+
14+
const Joi = require('@hapi/joi');
15+
16+
const ParamsDto = Joi.object({
17+
tagId: Joi.number()
18+
.integer()
19+
.positive(),
20+
});
21+
22+
const QueryDto = Joi.object({
23+
token: Joi.string(),
24+
});
25+
26+
const GetLogDto = Joi.object({
27+
body: Joi.object({}),
28+
params: ParamsDto,
29+
query: QueryDto,
30+
});
31+
32+
module.exports = GetLogDto;

lib/domain/dtos/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ const CreateLogDto = require('./CreateLogDto');
1515
const GetAllLogsDto = require('./GetAllLogsDto');
1616
const GetAllTagsDto = require('./GetAllTagsDto');
1717
const GetLogDto = require('./GetLogDto');
18+
const GetTagDto = require('./GetTagDto');
1819
const PaginationDto = require('./PaginationDto');
1920

2021
module.exports = {
2122
CreateLogDto,
2223
GetAllLogsDto,
2324
GetAllTagsDto,
2425
GetLogDto,
26+
GetTagDto,
2527
PaginationDto,
2628
};

lib/server/controllers/tags.controller.js

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
* or submit itself to any jurisdiction.
1212
*/
1313

14-
const { tag: { GetAllTagsUseCase } } = require('../../usecases');
15-
const { dtos: { GetAllTagsDto } } = require('../../domain');
14+
const { tag: { GetAllTagsUseCase, GetTagUseCase } } = require('../../usecases');
15+
const { dtos: { GetAllTagsDto, GetTagDto } } = require('../../domain');
1616
const { dtoValidator } = require('../utilities');
1717

1818
/**
@@ -182,24 +182,38 @@ const patchRun = (request, response, next) => {
182182
* next middleware function.
183183
* @returns {undefined}
184184
*/
185-
const read = (request, response, next) => {
186-
response.status(501).json({
187-
errors: [
188-
{
189-
status: '501',
190-
title: 'Not implemented',
191-
},
192-
],
193-
});
185+
const getTagById = async (request, response, next) => {
186+
const value = await dtoValidator(GetTagDto, request, response);
187+
if (!value) {
188+
return;
189+
}
190+
191+
const tag = await new GetTagUseCase()
192+
.execute(value);
193+
194+
if (tag === null) {
195+
response.status(404).json({
196+
errors: [
197+
{
198+
status: '404',
199+
title: `Tag with this id (${value.params.tagId}) could not be found`,
200+
},
201+
],
202+
});
203+
} else {
204+
response.status(200).json({
205+
data: tag,
206+
});
207+
}
194208
};
195209

196210
module.exports = {
197211
create,
198212
deleteTag,
199213
getLogs,
200214
getRuns,
215+
getTagById,
201216
listTags,
202217
patchLog,
203218
patchRun,
204-
read,
205219
};

lib/server/routers/tags.router.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ module.exports = {
2828
},
2929
{
3030
method: 'get',
31-
path: ':id',
32-
controller: TagsController.read,
31+
path: ':tagId',
32+
controller: TagsController.getTagById,
3333
children: [
3434
{
3535
method: 'get',

lib/usecases/tag/GetTagUseCase.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* @license
3+
* Copyright CERN and copyright holders of ALICE O2. This software is
4+
* distributed under the terms of the GNU General Public License v3 (GPL
5+
* Version 3), copied verbatim in the file "COPYING".
6+
*
7+
* See http://alice-o2.web.cern.ch/license for full licensing information.
8+
*
9+
* In applying this license CERN does not waive the privileges and immunities
10+
* granted to it by virtue of its status as an Intergovernmental Organization
11+
* or submit itself to any jurisdiction.
12+
*/
13+
14+
const {
15+
repositories: {
16+
TagRepository,
17+
},
18+
utilities: {
19+
QueryBuilder,
20+
TransactionHelper,
21+
},
22+
} = require('../../database');
23+
24+
/**
25+
* GetTagUseCase
26+
*/
27+
class GetTagUseCase {
28+
/**
29+
* Executes this use case.
30+
*
31+
* @param {Object} dto The GetTagDto containing all data.
32+
* @returns {Promise} Promise object represents the result of this use case.
33+
*/
34+
async execute(dto) {
35+
const queryBuilder = new QueryBuilder();
36+
const { params } = dto;
37+
const { tagId } = params;
38+
39+
queryBuilder.where('id', tagId);
40+
41+
return TransactionHelper.provide(() => TagRepository.findOne(queryBuilder));
42+
}
43+
}
44+
45+
module.exports = GetTagUseCase;

lib/usecases/tag/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
*/
1313

1414
const GetAllTagsUseCase = require('./GetAllTagsUseCase');
15+
const GetTagUseCase = require('./GetTagUseCase');
1516

1617
module.exports = {
1718
GetAllTagsUseCase,
19+
GetTagUseCase,
1820
};

spec/openapi.yaml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,23 @@ paths:
119119
$ref: '#/components/responses/UnexpectedError'
120120
tags:
121121
- tag
122+
/tags/{tagId}:
123+
parameters:
124+
- $ref: '#/components/parameters/TagId'
125+
get:
126+
operationId: getTagById
127+
summary: Gets a tag by Id
128+
responses:
129+
'200':
130+
$ref: '#/components/responses/Tag'
131+
'400':
132+
$ref: '#/components/responses/BadRequest'
133+
'404':
134+
$ref: '#/components/responses/NotFound'
135+
default:
136+
$ref: '#/components/responses/UnexpectedError'
137+
tags:
138+
- tag
122139

123140
components:
124141
parameters:
@@ -169,6 +186,14 @@ components:
169186
- -id
170187
style: form
171188
explode: false
189+
TagId:
190+
name: tagId
191+
description: The id of the tag to retrieve
192+
in: path
193+
required: true
194+
schema:
195+
type: integer
196+
format: int64
172197
responses:
173198
ArrayOfLogs:
174199
description: Expected response to a valid request.
@@ -206,6 +231,12 @@ components:
206231
application/json:
207232
schema:
208233
$ref: '#/components/schemas/Errors'
234+
Tag:
235+
description: Expected response to a valid request.
236+
content:
237+
application/json:
238+
schema:
239+
$ref: '#/components/schemas/TagResponse'
209240
UnexpectedError:
210241
description: Unexpected Error
211242
content:
@@ -344,3 +375,11 @@ components:
344375
- id
345376
- text
346377
additionalProperties: false
378+
TagResponse:
379+
description: Response containing a single tag.
380+
type: object
381+
properties:
382+
data:
383+
$ref: '#/components/schemas/Tag'
384+
required:
385+
- data
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* @license
3+
* Copyright CERN and copyright holders of ALICE O2. This software is
4+
* distributed under the terms of the GNU General Public License v3 (GPL
5+
* Version 3), copied verbatim in the file "COPYING".
6+
*
7+
* See http://alice-o2.web.cern.ch/license for full licensing information.
8+
*
9+
* In applying this license CERN does not waive the privileges and immunities
10+
* granted to it by virtue of its status as an Intergovernmental Organization
11+
* or submit itself to any jurisdiction.
12+
*/
13+
14+
const { tag: { GetTagUseCase } } = require('../../../../lib/usecases');
15+
const { dtos: { GetTagDto } } = require('../../../../lib/domain');
16+
const chai = require('chai');
17+
18+
const { expect } = chai;
19+
20+
module.exports = () => {
21+
let getLogDto;
22+
23+
beforeEach(async () => {
24+
getLogDto = await GetTagDto.validateAsync({
25+
params: {
26+
tagId: 1,
27+
},
28+
});
29+
});
30+
31+
it('should return an object that has the `entryId` property', async () => {
32+
const result = await new GetTagUseCase()
33+
.execute(getLogDto);
34+
35+
expect(result).to.have.ownProperty('id');
36+
expect(result.id).to.equal(1);
37+
});
38+
};

test/application/usecases/tag/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
*/
1313

1414
const GetAllTagsUseCase = require('./GetAllTagsUseCase.test');
15+
const GetTagUseCase = require('./GetTagUseCase.test');
1516

1617
module.exports = () => {
1718
describe('GetAllTagsUseCase', GetAllTagsUseCase);
19+
describe('GetTagUseCase', GetTagUseCase);
1820
};

test/e2e/tags.test.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,107 @@ module.exports = () => {
104104
});
105105
});
106106
});
107+
108+
describe('GET /api/tags/:tagId', () => {
109+
it('should return 400 if the tag id is not a number', (done) => {
110+
request(server)
111+
.get('/api/tags/abc')
112+
.expect(400)
113+
.end((err, res) => {
114+
if (err) {
115+
done(err);
116+
return;
117+
}
118+
119+
// Response must satisfy the OpenAPI specification
120+
expect(res).to.satisfyApiSpec;
121+
122+
const { errors } = res.body;
123+
const titleError = errors.find((err) => err.source.pointer === '/data/attributes/params/tagId');
124+
expect(titleError.detail).to.equal('"params.tagId" must be a number');
125+
126+
done();
127+
});
128+
});
129+
130+
it('should return 400 if the tag id is not positive', (done) => {
131+
request(server)
132+
.get('/api/tags/-1')
133+
.expect(400)
134+
.end((err, res) => {
135+
if (err) {
136+
done(err);
137+
return;
138+
}
139+
140+
// Response must satisfy the OpenAPI specification
141+
expect(res).to.satisfyApiSpec;
142+
143+
const { errors } = res.body;
144+
const titleError = errors.find((err) => err.source.pointer === '/data/attributes/params/tagId');
145+
expect(titleError.detail).to.equal('"params.tagId" must be a positive number');
146+
147+
done();
148+
});
149+
});
150+
151+
it('should return 400 if the tag id is not a whole number', (done) => {
152+
request(server)
153+
.get('/api/tags/0.5')
154+
.expect(400)
155+
.end((err, res) => {
156+
if (err) {
157+
done(err);
158+
return;
159+
}
160+
161+
// Response must satisfy the OpenAPI specification
162+
expect(res).to.satisfyApiSpec;
163+
164+
const { errors } = res.body;
165+
const titleError = errors.find((err) => err.source.pointer === '/data/attributes/params/tagId');
166+
expect(titleError.detail).to.equal('"params.tagId" must be an integer');
167+
168+
done();
169+
});
170+
});
171+
172+
it('should return 404 if the tag could not be found', (done) => {
173+
request(server)
174+
.get('/api/tags/999999999')
175+
.expect(404)
176+
.end((err, res) => {
177+
if (err) {
178+
done(err);
179+
return;
180+
}
181+
182+
// Response must satisfy the OpenAPI specification
183+
expect(res).to.satisfyApiSpec;
184+
185+
expect(res.body.errors[0].title).to.equal('Tag with this id (999999999) could not be found');
186+
187+
done();
188+
});
189+
});
190+
191+
it('should return 200 in all other cases', (done) => {
192+
request(server)
193+
.get('/api/tags/1')
194+
.expect(200)
195+
.end((err, res) => {
196+
if (err) {
197+
done(err);
198+
return;
199+
}
200+
201+
// Response must satisfy the OpenAPI specification
202+
expect(res).to.satisfyApiSpec;
203+
204+
expect(res.body.data.id).to.equal(1);
205+
206+
done();
207+
});
208+
});
209+
});
107210
};

0 commit comments

Comments
 (0)