Skip to content

Commit f6bdfd7

Browse files
committed
tests: Added e2e testing for card attachment endpoints
1 parent 06cbdc9 commit f6bdfd7

File tree

15 files changed

+607
-83
lines changed

15 files changed

+607
-83
lines changed

api/src/application/use-cases/card-attachment/DeleteCardAttachmentUseCase.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ class DeleteCardAttachmentUseCase {
1414
cardAttachment.publicId,
1515
);
1616

17-
if (deletedInStorage?.result !== 'ok')
17+
if (deletedInStorage?.result !== 'ok') {
1818
throw Boom.badRequest('Failed to delete file from Cloudinary');
19+
}
1920
}
2021

2122
const result = await this.cardAttachmentRepository.delete(

api/src/application/use-cases/card-attachment/SaveCardAttachmentUseCase.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ class SaveCardAttachmentUseCase {
88
}
99

1010
async execute(cardAttachmentData) {
11-
if (!cardAttachmentData?.id) {
12-
throw new Error('cardAttachment was not provided');
13-
}
1411
if (!cardAttachmentData?.cardId) throw new Error('cardId was not provided');
15-
if (!cardAttachmentData?.type || !cardAttachmentData?.publicId) {
12+
if (
13+
!cardAttachmentData?.type ||
14+
(cardAttachmentData.type !== 'external-link' &&
15+
!cardAttachmentData?.publicId)
16+
) {
1617
throw new Error(
1718
'cardAttachmentData does not contain the data of the saved attachment',
1819
);

api/src/application/use-cases/card-attachment/UpdateCardAttachmentUseCase.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ class UpdateCardAttachmentUseCase {
1515
if (cardAttachment.type !== 'external-link') {
1616
// eslint-disable-next-line no-unused-expressions, no-param-reassign
1717
delete cardAttachmentData.url || null;
18+
19+
if (!cardAttachmentData?.filename) {
20+
throw Boom.badRequest('filename was not provided to update attachment');
21+
}
1822
}
1923

2024
const entityUpdateCardAttachment = new EntityUpdateCardAttachment(

api/src/domain/entities/EntityUpdateCardAttachment.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
const boom = require('@hapi/boom');
12
const CardAttachmentFileName = require('../value-objects/cardAttachmentFileName');
23
const CardAttachmentUrl = require('../value-objects/cardAttachmentUrl');
34

45
class EntityUpdateCardAttachment {
56
constructor({ filename, url }) {
6-
this.filename = new CardAttachmentFileName(filename).value;
7+
if (!filename && !url) {
8+
throw boom.badRequest(
9+
'At least one of them must be provided (filename, url)',
10+
);
11+
}
12+
13+
if (filename) this.filename = new CardAttachmentFileName(filename).value;
714
if (url) this.url = new CardAttachmentUrl(url).value;
815
}
916
}

api/src/infrastructure/store/db/seeders/14-card-attachments.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ module.exports = {
1818
{
1919
id: 'a5332d0a-6352-4599-89e6-48c70908f6ba',
2020
filename: 'Agroplus-db-scheme-2.jpg',
21-
url: 'https://res.cloudinary.com/dfprxzekh/image/upload/v1744326304/card-attachments/file_ysjlp7.jpg',
21+
url: 'https://github.com/OscarS05/Trello-like-project-api',
2222
card_id: '4600a009-a471-4416-bf24-e857d23d2ab3',
23-
type: 'image/jpeg',
23+
type: 'external-link',
2424
created_at: '2025-04-10T23:05:04.884Z',
2525
},
2626

api/src/interfaces/controllers/card-attachments.controller.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,15 @@ const updateCardAttachment = async (req, res, next) => {
6767
const { cardId, attachmentId } = req.params;
6868
const cardAttachmentData = req.body;
6969

70-
const upadedCard = await cardAttachmentService.updateCardAttachment(
70+
const upatedCard = await cardAttachmentService.updateCardAttachment(
7171
cardId,
7272
attachmentId,
7373
cardAttachmentData,
7474
);
7575

7676
res.status(200).json({
7777
message: 'The card attachment was successfully updated',
78-
upadedCard,
78+
upatedCard,
7979
});
8080
} catch (error) {
8181
next(error);

api/src/interfaces/middlewares/authorization/card.authorization.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const checkProjectMembershipByCard = async (req, res, next) => {
5353
cardId,
5454
);
5555
if (!projectMember?.id)
56-
throw boom.notFound('Something went wrong with data');
56+
throw boom.forbidden('Something went wrong with data');
5757

5858
req.projectMember = projectMember;
5959
next();

api/src/interfaces/middlewares/upload-files.handler.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const multer = require('multer');
2+
const boom = require('@hapi/boom');
23
const path = require('path');
34

45
const { validatorHandler } = require('./validator.handler');
@@ -12,7 +13,7 @@ const fileFilterByExtension = (allowedFormats) => {
1213
if (allowedFormats.includes(ext)) {
1314
cb(null, true);
1415
} else {
15-
cb(new Error(`Invalid file type: ${ext}`), false);
16+
cb(boom.badRequest(`Invalid file type: ${ext}`), false);
1617
}
1718
};
1819
};

api/src/interfaces/routes/auth.router.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ const {
1212

1313
const EMAIL_LIMITER_MESSAGE =
1414
'Too many email requests, please try again after an 15 minutes';
15-
const LOGIN_LIMITER_MESSAGE = {
16-
error: 'Too many login attempts',
17-
message: 'Please wait 15 minutes and try again.',
18-
};
15+
// const LOGIN_LIMITER_MESSAGE = {
16+
// error: 'Too many login attempts',
17+
// message: 'Please wait 15 minutes and try again.',
18+
// };
1919

2020
const {
2121
login,
@@ -58,7 +58,7 @@ const {
5858
* 429:
5959
* description: Too many login attempts, please try again later.
6060
*/
61-
router.post('/login', limiter(6, 15 * 60 * 100, LOGIN_LIMITER_MESSAGE), login);
61+
router.post('/login', login);
6262

6363
/**
6464
* @swagger

api/src/interfaces/routes/card-attachments.router.js

Lines changed: 61 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,67 @@ router.get(
7070
cardAttachmentControllers.getAllCardAttachments,
7171
);
7272

73+
/**
74+
* @swagger
75+
* /cards/{cardId}/attachments/{attachmentId}/download:
76+
* get:
77+
* summary: Download a file attachment from a card
78+
* description: |
79+
* It acts as a proxy for downloading an attachment stored on an external hosting service. It can also be used to view card attachments, not just for downloading.
80+
*
81+
* - This endpoint ensures the actual storage URL remains hidden.
82+
* - Only works for **file attachments**, not for external links.
83+
* - The attachment must belong to the card identified by `cardId`.
84+
*
85+
* ### Authorization & Access Rules
86+
* - Requires a valid Bearer access token.
87+
* - Both `cardId` and `attachmentId` must be valid UUIDs.
88+
* - The requester must be a member of the project that owns the card (any role).
89+
*
90+
* tags:
91+
* - card-attachment
92+
* security:
93+
* - bearerAuth: []
94+
* parameters:
95+
* - in: path
96+
* name: cardId
97+
* required: true
98+
* description: ID of the card that owns the attachment
99+
* schema:
100+
* type: string
101+
* format: uuid
102+
* - in: path
103+
* name: attachmentId
104+
* required: true
105+
* description: ID of the file attachment to download
106+
* schema:
107+
* type: string
108+
* format: uuid
109+
* responses:
110+
* 200:
111+
* description: File attachment streamed for download
112+
* content:
113+
* application/octet-stream:
114+
* schema:
115+
* type: string
116+
* format: binary
117+
* 400:
118+
* description: Cannot download external links
119+
* 401:
120+
* description: Invalid or missing token
121+
* 403:
122+
* description: User is not authorized to download this attachment
123+
* 404:
124+
* description: Attachment not found or does not belong to the specified card
125+
*/
126+
router.get(
127+
'/:cardId/attachments/:attachmentId/download',
128+
validateSession,
129+
validatorHandler(cardAttachmentSchema, 'params'),
130+
checkProjectMembershipByCard,
131+
cardAttachmentControllers.downloadCardAttachment,
132+
);
133+
73134
/**
74135
* @swagger
75136
* /cards/{cardId}/attachments:
@@ -310,65 +371,4 @@ router.delete(
310371
cardAttachmentControllers.deleteCardAttachent,
311372
);
312373

313-
/**
314-
* @swagger
315-
* /cards/{cardId}/attachments/{attachmentId}/download:
316-
* get:
317-
* summary: Download a file attachment from a card
318-
* description: |
319-
* It acts as a proxy for downloading an attachment stored on an external hosting service. It can also be used to view card attachments, not just for downloading.
320-
*
321-
* - This endpoint ensures the actual storage URL remains hidden.
322-
* - Only works for **file attachments**, not for external links.
323-
* - The attachment must belong to the card identified by `cardId`.
324-
*
325-
* ### Authorization & Access Rules
326-
* - Requires a valid Bearer access token.
327-
* - Both `cardId` and `attachmentId` must be valid UUIDs.
328-
* - The requester must be a member of the project that owns the card (any role).
329-
*
330-
* tags:
331-
* - card-attachment
332-
* security:
333-
* - bearerAuth: []
334-
* parameters:
335-
* - in: path
336-
* name: cardId
337-
* required: true
338-
* description: ID of the card that owns the attachment
339-
* schema:
340-
* type: string
341-
* format: uuid
342-
* - in: path
343-
* name: attachmentId
344-
* required: true
345-
* description: ID of the file attachment to download
346-
* schema:
347-
* type: string
348-
* format: uuid
349-
* responses:
350-
* 200:
351-
* description: File attachment streamed for download
352-
* content:
353-
* application/octet-stream:
354-
* schema:
355-
* type: string
356-
* format: binary
357-
* 400:
358-
* description: Cannot download external links
359-
* 401:
360-
* description: Invalid or missing token
361-
* 403:
362-
* description: User is not authorized to download this attachment
363-
* 404:
364-
* description: Attachment not found or does not belong to the specified card
365-
*/
366-
router.get(
367-
'/:cardId/attachments/:attachmentId/download',
368-
validateSession,
369-
validatorHandler(cardAttachmentSchema, 'params'),
370-
checkProjectMembershipByCard,
371-
cardAttachmentControllers.downloadCardAttachment,
372-
);
373-
374374
module.exports = router;

0 commit comments

Comments
 (0)