diff --git a/src/exported b/src/exported index e2ed2db0..38a92ceb 160000 --- a/src/exported +++ b/src/exported @@ -1 +1 @@ -Subproject commit e2ed2db07067b350fb8847d72dbcde53c9d89136 +Subproject commit 38a92ceb6528c52ac6eab1846e26346cf1ab0c70 diff --git a/src/i18n/en-US/validations.json b/src/i18n/en-US/validations.json index b155c97a..bf40953d 100644 --- a/src/i18n/en-US/validations.json +++ b/src/i18n/en-US/validations.json @@ -37,7 +37,10 @@ "file": { "invalid": { "not_provided": "File not provided", - "not_found": "File '{filename}' cannot be found on disk", + "not_found": { + "on_disk": "File '{filename}' cannot be found on disk", + "by_id": "File with ID '{id}' not found" + }, "no_mime_type": "File has no mime type", "unauthorized_mime_type": "File has an unauthorized mime type, valid mime types are ['{mime_types}']", "infected": "File is infected with virus" diff --git a/src/modules/files/dto/output.dto.ts b/src/modules/files/dto/output.dto.ts index e8d21571..21e2f960 100644 --- a/src/modules/files/dto/output.dto.ts +++ b/src/modules/files/dto/output.dto.ts @@ -31,6 +31,9 @@ export class OutputFileDTO implements OutputFileDto { @ApiProperty() description?: string; + + @ApiProperty() + owner: { kind: 'user' | 'promotion'; id: number }; } export class OutputFileVisibilityGroupDTO extends OutputBaseDTO implements OutputFileVisibilityGroupDto { diff --git a/src/modules/files/files.controller.ts b/src/modules/files/files.controller.ts new file mode 100644 index 00000000..79c11139 --- /dev/null +++ b/src/modules/files/files.controller.ts @@ -0,0 +1,32 @@ +import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; +import { ApiBearerAuth, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { ApiNotOkResponses } from '@modules/base/decorators'; +import { InputIdParamDTO } from '@modules/base/dto/input.dto'; +import { Request } from '@modules/users/entities/user.entity'; + +import { OutputFileDTO } from './dto/output.dto'; +import { FilesService } from './files.service'; + +@ApiTags('Files') +@Controller('files') +@UseGuards(AuthGuard('jwt')) +@ApiBearerAuth() +export class FilesController { + constructor(private readonly filesService: FilesService) {} + + @Get(':id/data') + @ApiOperation({ summary: 'Get file data' }) + @ApiParam({ name: 'id', description: 'The file ID' }) + @ApiOkResponse({ type: OutputFileDTO }) + @ApiNotOkResponses({ + 400: 'Invalid ID', + 401: 'Not in file visibility group', + 404: 'File not found', + }) + async getFile(@Req() req: Request, @Param() params: InputIdParamDTO): Promise { + const file = await this.filesService.findOne(params.id); + return this.filesService.getAsData(file, req.user.id); + } +} diff --git a/src/modules/files/files.module.ts b/src/modules/files/files.module.ts index 07bd5d4d..28e2166b 100644 --- a/src/modules/files/files.module.ts +++ b/src/modules/files/files.module.ts @@ -5,12 +5,14 @@ import { EmailsService } from '@modules/emails/emails.service'; import { UsersDataService } from '@modules/users/services/users-data.service'; import { FileVisibilityGroup } from './entities/file-visibility.entity'; +import { FilesController } from './files.controller'; import { FilesService } from './files.service'; import { ImagesService } from './images.service'; @Module({ imports: [MikroOrmModule.forFeature([FileVisibilityGroup])], providers: [EmailsService, FilesService, ImagesService, UsersDataService], + controllers: [FilesController], exports: [FilesService], }) export class FilesModule {} diff --git a/src/modules/files/files.service.ts b/src/modules/files/files.service.ts index 62044d6c..962c2892 100644 --- a/src/modules/files/files.service.ts +++ b/src/modules/files/files.service.ts @@ -3,7 +3,7 @@ import { accessSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs'; import { join } from 'path'; import { Readable } from 'stream'; -import { MikroORM, CreateRequestContext } from '@mikro-orm/core'; +import { MikroORM, CreateRequestContext, Loaded } from '@mikro-orm/core'; import { Injectable, StreamableFile } from '@nestjs/common'; import { fromBuffer, MimeType } from 'file-type'; @@ -11,8 +11,9 @@ import { i18nBadRequestException, i18nNotFoundException, i18nUnauthorizedExcepti import { User } from '@modules/users/entities/user.entity'; import { UsersDataService } from '@modules/users/services/users-data.service'; +import { OutputFileDTO } from './dto/output.dto'; import { FileVisibilityGroup } from './entities/file-visibility.entity'; -import { File } from './entities/file.entity'; +import { File, FileKind } from './entities/file.entity'; export type WriteFileOptions = { directory: string; @@ -39,6 +40,22 @@ export class FilesService { return new StreamableFile(this.toReadable(file)); } + async getAsData(file: File, user_id: number): Promise { + if (!(await this.canReadFile(file, user_id))) + throw new i18nUnauthorizedException('validations.user.invalid.not_in_file_visibility_group', { + group_name: file.visibility?.name, + }); + + return file.toObject() as unknown as OutputFileDTO; + } + + async findOne(id: number): Promise { + const file = (await this.orm.em.findOne(File, { id })) as unknown as Loaded; + if (!file) throw new i18nNotFoundException('validations.file.invalid.not_found.by_id', { id }); + + return file; + } + /** * Determine if the given user can read the given file. * @param {File} file - The file to check the visibility of. @@ -135,12 +152,12 @@ export class FilesService { * @param {File} file The file to delete * @param {boolean} silent If true, the function will not throw an error if the file doesn't exist */ - deleteFromDisk(file: File, silent: boolean = true) { + deleteFromDisk(file: File, silent: boolean = true) { try { accessSync(file.path); } catch { if (silent) return; - throw new i18nNotFoundException('validations.file.invalid.not_found', { + throw new i18nNotFoundException('validations.file.invalid.not_found.on_disk', { filename: file.filename, }); } @@ -157,7 +174,7 @@ export class FilesService { try { accessSync(file.path); } catch { - throw new i18nNotFoundException('validations.file.invalid.not_found', { + throw new i18nNotFoundException('validations.file.invalid.not_found.on_disk', { filename: file.filename, }); } diff --git a/src/modules/files/images.service.ts b/src/modules/files/images.service.ts index c7974f46..995e0ede 100644 --- a/src/modules/files/images.service.ts +++ b/src/modules/files/images.service.ts @@ -16,8 +16,15 @@ type WriteImageOptions = WriteFileOptions & { export class ImagesService extends FilesService { async validateAspectRatio(buffer: Buffer, aspect_ratio: aspect_ratio): Promise { const { width, height } = await sharp(buffer).metadata(); - const [aspectWidth, aspectHeight] = aspect_ratio.split(':').map((s) => parseInt(s, 10)); - return Math.abs(width / height - aspectWidth / aspectHeight) < Number.EPSILON; + + const gcd = (...arr: number[]): number => { + const _gcd = (x: number, y: number) => (!y ? x : gcd(y, x % y)); + return [...arr].reduce((a, b) => _gcd(a, b)); + }; + + const gcdResult = gcd(width, height); + + return `${width / gcdResult}:${height / gcdResult}` === aspect_ratio; } async convertToWebp(buffer: Buffer): Promise { diff --git a/src/modules/promotions/dto/output.dto.ts b/src/modules/promotions/dto/output.dto.ts index 2147d5b5..b388aed1 100644 --- a/src/modules/promotions/dto/output.dto.ts +++ b/src/modules/promotions/dto/output.dto.ts @@ -1,9 +1,8 @@ -import type { OutputPromotionPictureDto, OutputPromotionDto } from '#types/api'; +import type { OutputPromotionDto } from '#types/api'; import { ApiProperty } from '@nestjs/swagger'; import { OutputBaseDTO } from '@modules/base/dto/output.dto'; -import { OutputFileDTO } from '@modules/files/dto/output.dto'; export class OutputPromotionDTO extends OutputBaseDTO implements OutputPromotionDto { @ApiProperty() @@ -15,8 +14,3 @@ export class OutputPromotionDTO extends OutputBaseDTO implements OutputPromotion @ApiProperty({ required: false }) picture?: number; } - -export class OutputPromotionPictureDTO extends OutputFileDTO implements OutputPromotionPictureDto { - @ApiProperty() - picture_promotion_id: number; -} diff --git a/src/modules/promotions/entities/promotion-picture.entity.ts b/src/modules/promotions/entities/promotion-picture.entity.ts index 7c968aef..873638cf 100644 --- a/src/modules/promotions/entities/promotion-picture.entity.ts +++ b/src/modules/promotions/entities/promotion-picture.entity.ts @@ -9,8 +9,8 @@ export class PromotionPicture extends File { @OneToOne(() => Promotion, (promotion) => promotion.picture, { nullable: true, owner: true, - serializedName: 'picture_promotion_id', - serializer: (p: Promotion) => p.id, + serializedName: 'owner', + serializer: (p: Promotion) => ({ kind: 'promotion', id: p?.id }), }) picture_promotion: Promotion; diff --git a/src/modules/promotions/promotions.controller.ts b/src/modules/promotions/promotions.controller.ts index 57305f70..d868300a 100644 --- a/src/modules/promotions/promotions.controller.ts +++ b/src/modules/promotions/promotions.controller.ts @@ -9,6 +9,7 @@ import { OutputMessageDTO } from '@modules/base/dto/output.dto'; import { i18nBadRequestException } from '@modules/base/http-errors'; import { ApiDownloadFile } from '@modules/files/decorators/download.decorator'; import { ApiUploadFile } from '@modules/files/decorators/upload.decorator'; +import { OutputFileDTO } from '@modules/files/dto/output.dto'; import { FilesService } from '@modules/files/files.service'; import { Request } from '@modules/users/entities/user.entity'; @@ -57,9 +58,12 @@ export class PromotionsController { @ApiOperation({ summary: 'Update the promotion logo' }) @ApiUploadFile() @ApiParam({ name: 'number', description: 'The promotion number (eg: 21)' }) - @ApiOkResponse({ type: OutputPromotionDTO }) + @ApiOkResponse({ type: OutputFileDTO }) @ApiNotOkResponses({ 400: 'Invalid file', 404: 'Promotion not found' }) - async editLogo(@UploadedFile() file: Express.Multer.File, @Param() params: InputPromotionNumberParamDTO) { + async editLogo( + @UploadedFile() file: Express.Multer.File, + @Param() params: InputPromotionNumberParamDTO, + ): Promise { if (!file) throw new i18nBadRequestException('validations.file.invalid.not_provided'); return this.promotionsService.updateLogo(params.number, file); diff --git a/src/modules/promotions/promotions.service.ts b/src/modules/promotions/promotions.service.ts index d5f696b9..36317ea7 100644 --- a/src/modules/promotions/promotions.service.ts +++ b/src/modules/promotions/promotions.service.ts @@ -7,9 +7,10 @@ import { Cron } from '@nestjs/schedule'; import { env } from '@env'; import { OutputMessageDTO } from '@modules/base/dto/output.dto'; import { i18nNotFoundException } from '@modules/base/http-errors'; +import { OutputFileDTO } from '@modules/files/dto/output.dto'; import { ImagesService } from '@modules/files/images.service'; -import { OutputPromotionPictureDTO, OutputPromotionDTO } from './dto/output.dto'; +import { OutputPromotionDTO } from './dto/output.dto'; import { PromotionPicture } from './entities/promotion-picture.entity'; import { Promotion } from './entities/promotion.entity'; import { OutputBaseUserDTO } from '../users/dto/output.dto'; @@ -90,7 +91,7 @@ export class PromotionsService { } @CreateRequestContext() - async updateLogo(number: number, file: Express.Multer.File): Promise { + async updateLogo(number: number, file: Express.Multer.File): Promise { const promotion = await this.orm.em.findOne(Promotion, { number }, { populate: ['picture'] }); if (!promotion) throw new i18nNotFoundException('validations.promotion.invalid.not_found', { number }); @@ -120,7 +121,7 @@ export class PromotionsService { }); await this.orm.em.persistAndFlush(promotion); - return promotion.picture.toObject() as unknown as OutputPromotionPictureDTO; + return promotion.picture.toObject() as unknown as OutputFileDTO; } @CreateRequestContext() diff --git a/src/modules/users/controllers/users-files.controller.ts b/src/modules/users/controllers/users-files.controller.ts index 70d93720..8d90b80c 100644 --- a/src/modules/users/controllers/users-files.controller.ts +++ b/src/modules/users/controllers/users-files.controller.ts @@ -12,9 +12,9 @@ import { OutputMessageDTO } from '@modules/base/dto/output.dto'; import { i18nBadRequestException } from '@modules/base/http-errors'; import { ApiDownloadFile } from '@modules/files/decorators/download.decorator'; import { ApiUploadFile } from '@modules/files/decorators/upload.decorator'; +import { OutputFileDTO } from '@modules/files/dto/output.dto'; import { FilesService } from '@modules/files/files.service'; -import { OutputUserBannerDTO, OutputUserPictureDTO } from '../dto/output.dto'; import { Request } from '../entities/user.entity'; import { UsersFilesService } from '../services/users-files.service'; @@ -30,14 +30,14 @@ export class UsersFilesController { @GuardSelfOrPermissions('id', ['CAN_EDIT_USER']) @ApiOperation({ summary: 'Update user profile picture' }) @ApiParam({ name: 'id', description: 'The user ID' }) - @ApiOkResponse({ description: 'The updated user picture', type: OutputUserPictureDTO }) + @ApiOkResponse({ description: 'The updated user picture', type: OutputFileDTO }) @ApiNotOkResponses({ 400: 'Invalid user ID or missing uploaded file', 404: 'User not found' }) @ApiUploadFile() async editPicture( @Req() req: Request, @Param() params: InputIdParamDTO, @UploadedFile() file: Express.Multer.File, - ): Promise { + ): Promise { if (!file) throw new i18nBadRequestException('validations.file.invalid.not_provided'); return this.usersFilesService.updatePicture(req.user, params.id, file); } @@ -68,13 +68,13 @@ export class UsersFilesController { @GuardSelfOrPermissions('id', ['CAN_EDIT_USER']) @ApiOperation({ summary: 'Update user profile banner' }) @ApiParam({ name: 'id', description: 'The user ID' }) - @ApiOkResponse({ description: 'The updated user banner', type: OutputUserBannerDTO }) + @ApiOkResponse({ description: 'The updated user banner', type: OutputFileDTO }) @ApiNotOkResponses({ 400: 'Invalid user ID or missing uploaded file', 404: 'User not found' }) @ApiUploadFile() async editBanner( @Param() params: InputIdParamDTO, @UploadedFile() file: Express.Multer.File, - ): Promise { + ): Promise { if (!file) throw new i18nBadRequestException('validations.file.invalid.not_provided'); return this.usersFilesService.updateBanner(params.id, file); } diff --git a/src/modules/users/dto/output.dto.ts b/src/modules/users/dto/output.dto.ts index 6805d126..8582e236 100644 --- a/src/modules/users/dto/output.dto.ts +++ b/src/modules/users/dto/output.dto.ts @@ -1,11 +1,5 @@ import type { email } from '#types'; -import type { - OutputUserBannerDto, - OutputUserPictureDto, - OutputUserRoleDto, - OutputUserVisibilityDto, - OutputBaseUserDto, -} from '#types/api'; +import type { OutputUserRoleDto, OutputUserVisibilityDto, OutputBaseUserDto } from '#types/api'; import { ApiProperty } from '@nestjs/swagger'; @@ -13,7 +7,6 @@ import { OutputUserDto, PERMISSION_NAMES, GENDERS } from '#types/api'; import { USER_GENDER } from '@exported/api/constants/genders'; import { PERMISSIONS_NAMES } from '@exported/api/constants/perms'; import { OutputBaseDTO } from '@modules/base/dto/output.dto'; -import { OutputFileDTO } from '@modules/files/dto/output.dto'; export class OutputBaseUserDTO extends OutputBaseDTO implements OutputBaseUserDto { @ApiProperty() @@ -102,7 +95,7 @@ export class OutputUserDTO extends OutputBaseDTO implements OutputUserDto { verified?: Date; } -export class OutputUserVisibilityDTO implements OutputUserVisibilityDto { +export class OutputUserVisibilityDTO extends OutputBaseDTO implements OutputUserVisibilityDto { @ApiProperty({ minimum: 1 }) user_id: number; @@ -130,13 +123,3 @@ export class OutputUserVisibilityDTO implements OutputUserVisibilityDto { @ApiProperty({ type: Boolean, default: false }) parents_phone: boolean; } - -export class OutputUserPictureDTO extends OutputFileDTO implements OutputUserPictureDto { - @ApiProperty({ minimum: 1 }) - picture_user_id: number; -} - -export class OutputUserBannerDTO extends OutputFileDTO implements OutputUserBannerDto { - @ApiProperty({ minimum: 1 }) - banner_user_id: number; -} diff --git a/src/modules/users/entities/user-banner.entity.ts b/src/modules/users/entities/user-banner.entity.ts index f6331c7e..cf3ec636 100644 --- a/src/modules/users/entities/user-banner.entity.ts +++ b/src/modules/users/entities/user-banner.entity.ts @@ -9,8 +9,8 @@ export class UserBanner extends File { @OneToOne(() => User, (user) => user.banner, { nullable: true, owner: true, - serializedName: 'banner_user_id', - serializer: (u: User) => u.id, + serializedName: 'owner', + serializer: (u: User) => ({ kind: 'user', id: u?.id }), }) banner_user: User; diff --git a/src/modules/users/entities/user-picture.entity.ts b/src/modules/users/entities/user-picture.entity.ts index d479ebde..ed89d884 100644 --- a/src/modules/users/entities/user-picture.entity.ts +++ b/src/modules/users/entities/user-picture.entity.ts @@ -9,8 +9,8 @@ export class UserPicture extends File { @OneToOne(() => User, (user) => user.picture, { nullable: true, owner: true, - serializedName: 'picture_user_id', - serializer: (u: User) => u.id, + serializedName: 'owner', + serializer: (u: User) => ({ kind: 'user', id: u?.id }), }) picture_user: User; diff --git a/src/modules/users/services/users-files.service.ts b/src/modules/users/services/users-files.service.ts index 87444623..81774720 100644 --- a/src/modules/users/services/users-files.service.ts +++ b/src/modules/users/services/users-files.service.ts @@ -6,10 +6,10 @@ import { Injectable } from '@nestjs/common'; import { env } from '@env'; import { OutputMessageDTO } from '@modules/base/dto/output.dto'; import { i18nNotFoundException, i18nUnauthorizedException } from '@modules/base/http-errors'; +import { OutputFileDTO } from '@modules/files/dto/output.dto'; import { ImagesService } from '@modules/files/images.service'; import { UsersDataService } from './users-data.service'; -import { OutputUserBannerDTO, OutputUserPictureDTO } from '../dto/output.dto'; import { UserBanner } from '../entities/user-banner.entity'; import { UserPicture } from '../entities/user-picture.entity'; import { User } from '../entities/user.entity'; @@ -30,7 +30,7 @@ export class UsersFilesService { * @returns {Promise} The updated user */ @CreateRequestContext() - async updatePicture(req_user: User, owner_id: number, file: Express.Multer.File): Promise { + async updatePicture(req_user: User, owner_id: number, file: Express.Multer.File): Promise { const user = await this.orm.em.findOne(User, { id: owner_id }, { populate: ['picture'] }); if (!user) throw new i18nNotFoundException('validations.user.not_found.id', { id: owner_id }); @@ -78,7 +78,7 @@ export class UsersFilesService { }); await this.orm.em.persistAndFlush(user); - return user.picture.toObject() as unknown as OutputUserPictureDTO; + return user.picture.toObject() as unknown as OutputFileDTO; } @CreateRequestContext() @@ -103,14 +103,14 @@ export class UsersFilesService { } @CreateRequestContext() - async updateBanner(id: number, file: Express.Multer.File): Promise { + async updateBanner(id: number, file: Express.Multer.File): Promise { const user = await this.orm.em.findOne(User, { id }, { populate: ['banner'] }); if (!user) throw new i18nNotFoundException('validations.user.not_found.id', { id }); const fileInfos = await this.imagesService.writeOnDisk(file.buffer, { directory: join(env.USERS_BASE_PATH, 'banners'), filename: user.full_name.replaceAll(' ', '_'), - aspect_ratio: '16:9', + aspect_ratio: '3:1', }); // Remove old file if present @@ -136,7 +136,7 @@ export class UsersFilesService { }); await this.orm.em.persistAndFlush(user); - return user.banner.toObject() as unknown as OutputUserBannerDTO; + return user.banner.toObject() as unknown as OutputFileDTO; } @CreateRequestContext() diff --git a/tests/e2e/files.e2e-spec.ts b/tests/e2e/files.e2e-spec.ts new file mode 100644 index 00000000..c31e20cb --- /dev/null +++ b/tests/e2e/files.e2e-spec.ts @@ -0,0 +1,133 @@ +import request from 'supertest'; + +import { OutputTokenDTO } from '@modules/auth/dto/output.dto'; +import { i18nBadRequestException, i18nNotFoundException, i18nUnauthorizedException } from '@modules/base/http-errors'; +import { FileVisibilityGroup } from '@modules/files/entities/file-visibility.entity'; +import { File } from '@modules/files/entities/file.entity'; +import { UserPicture } from '@modules/users/entities/user-picture.entity'; + +import { orm, server } from '..'; + +describe('Files (e2e)', () => { + let em: typeof orm.em; + let file: File; + + let tokenVerified: string; + + let tokenSubscriber: string; + let userIdSubscriber: number; + + beforeAll(async () => { + type res = Omit & { body: OutputTokenDTO }; + + em = orm.em.fork(); + + const responseA: res = await request(server).post('/auth/login').send({ + email: 'promos@email.com', + password: 'root', + }); + + tokenVerified = responseA.body.token; + + const responseB: res = await request(server).post('/auth/login').send({ + email: 'subscriber@email.com', + password: 'root', + }); + + tokenSubscriber = responseB.body.token; + userIdSubscriber = responseB.body.user_id; + + const visibility = await em.findOne(FileVisibilityGroup, { name: 'SUBSCRIBER' }); + file = em.create(UserPicture, { + filename: 'test.png', + mimetype: 'image/png', + path: 'test.png', + size: 123, + visibility, + description: 'foo bar', + picture_user: userIdSubscriber, + }); + + await em.persistAndFlush(file); + }); + + afterAll(async () => { + await em.removeAndFlush(file); + }); + + describe('(GET) /files/:id/data', () => { + describe('400 : Bad Request', () => { + it('when id is not valid', async () => { + const res = await request(server) + .get(`/files/invalid/data`) + .set('Authorization', `Bearer ${tokenSubscriber}`) + .expect(400); + + expect(res.body).toEqual({ + ...new i18nBadRequestException('validations.id.invalid.format', { property: 'id', value: 'invalid' }), + }); + }); + }); + + describe('401 : Unauthorized', () => { + it('when user is not logged in', async () => { + const res = await request(server).get(`/files/${file.id}/data`).expect(401); + + expect(res.body).toEqual({ + message: 'Unauthorized', + statusCode: 401, + }); + }); + + it('when user is not in the visibility group', async () => { + const res = await request(server) + .get(`/files/${file.id}/data`) + .set('Authorization', `Bearer ${tokenVerified}`) + .expect(401); + + expect(res.body).toEqual({ + ...new i18nUnauthorizedException('validations.user.invalid.not_in_file_visibility_group', { + group_name: file.visibility.name, + }), + }); + }); + }); + + describe('404 : Not Found', () => { + it('when file is not found', async () => { + const res = await request(server) + .get(`/files/9999/data`) + .set('Authorization', `Bearer ${tokenSubscriber}`) + .expect(404); + + expect(res.body).toEqual({ + ...new i18nNotFoundException('validations.file.invalid.not_found.by_id', { id: 9999 }), + }); + }); + }); + + describe('200 : Ok', () => { + it('when file is found', async () => { + const res = await request(server) + .get(`/files/${file.id}/data`) + .set('Authorization', `Bearer ${tokenSubscriber}`) + .expect(200); + + expect(res.body).toEqual({ + id: file.id, + filename: 'test.png', + mimetype: 'image/png', + size: 123, + visibility_id: file.visibility.id, + description: 'foo bar', + owner: { + kind: 'user', + id: userIdSubscriber, + }, + created: expect.any(String), + updated: expect.any(String), + }); + }); + }); + }); +}); diff --git a/tests/e2e/promotions.e2e-spec.ts b/tests/e2e/promotions.e2e-spec.ts index a26673d0..49edbe8c 100644 --- a/tests/e2e/promotions.e2e-spec.ts +++ b/tests/e2e/promotions.e2e-spec.ts @@ -509,7 +509,10 @@ describe('Promotions (e2e)', () => { created: expect.any(String), updated: expect.any(String), filename: expect.any(String), - picture_promotion_id: 21, + owner: { + id: 21, + kind: 'promotion', + }, mimetype: 'image/webp', size: 117280, }); @@ -535,7 +538,10 @@ describe('Promotions (e2e)', () => { created: expect.any(String), updated: expect.any(String), filename: expect.any(String), - picture_promotion_id: 21, + owner: { + id: 21, + kind: 'promotion', + }, description: null, mimetype: 'image/webp', size: 117280, diff --git a/tests/e2e/users/users-files.e2e-spec.ts b/tests/e2e/users/users-files.e2e-spec.ts index 0566beca..a2f04146 100644 --- a/tests/e2e/users/users-files.e2e-spec.ts +++ b/tests/e2e/users/users-files.e2e-spec.ts @@ -327,7 +327,10 @@ describe('Users Files (e2e)', () => { mimetype: 'image/webp', size: 3028, updated: expect.any(String), - picture_user_id: 4, + owner: { + id: 4, + kind: 'user', + }, visibility_id: 1, }); @@ -350,7 +353,10 @@ describe('Users Files (e2e)', () => { mimetype: 'image/webp', size: 3028, updated: expect.any(String), - picture_user_id: 4, + owner: { + id: 4, + kind: 'user', + }, visibility_id: 1, }); @@ -381,7 +387,10 @@ describe('Users Files (e2e)', () => { mimetype: 'image/webp', size: 3028, updated: expect.any(String), - picture_user_id: 1, + owner: { + id: 1, + kind: 'user', + }, visibility_id: 1, }); @@ -690,9 +699,12 @@ describe('Users Files (e2e)', () => { filename: expect.stringContaining('logs_moderator') as unknown, id: expect.any(Number), mimetype: 'image/webp', - size: 9498, + size: expect.any(Number), updated: expect.any(String), - banner_user_id: 4, + owner: { + id: 4, + kind: 'user', + }, visibility_id: 1, }); @@ -721,9 +733,12 @@ describe('Users Files (e2e)', () => { filename: expect.stringContaining('logs_moderator') as unknown, id: expect.any(Number), mimetype: 'image/webp', - size: 9498, + size: expect.any(Number), updated: expect.any(String), - banner_user_id: 4, + owner: { + id: 4, + kind: 'user', + }, visibility_id: 1, }); @@ -744,9 +759,12 @@ describe('Users Files (e2e)', () => { filename: expect.stringContaining('root_root') as unknown, id: expect.any(Number), mimetype: 'image/webp', - size: 9498, + size: expect.any(Number), updated: expect.any(String), - banner_user_id: 1, + owner: { + id: 1, + kind: 'user', + }, visibility_id: 1, }); diff --git a/tests/files/user_banner.jpeg b/tests/files/user_banner.jpeg index dabbdfbb..fc03c651 100644 Binary files a/tests/files/user_banner.jpeg and b/tests/files/user_banner.jpeg differ diff --git a/tests/units/services/files.test.ts b/tests/units/services/files.test.ts index aa0e5bf0..d1f1cc46 100644 --- a/tests/units/services/files.test.ts +++ b/tests/units/services/files.test.ts @@ -42,7 +42,9 @@ describe('FilesService (unit)', () => { it('should throw when the file cannot be accessed', () => { expect(() => { filesService.toReadable(fake_file); - }).toThrow(new i18nNotFoundException('validations.file.invalid.not_found', { filename: fake_file.filename })); + }).toThrow( + new i18nNotFoundException('validations.file.invalid.not_found.on_disk', { filename: fake_file.filename }), + ); }); }); @@ -54,7 +56,9 @@ describe('FilesService (unit)', () => { it('should throw when asked if the file does not exist', () => { expect(() => { filesService.deleteFromDisk(fake_file, false); - }).toThrow(new i18nNotFoundException('validations.file.invalid.not_found', { filename: fake_file.filename })); + }).toThrow( + new i18nNotFoundException('validations.file.invalid.not_found.on_disk', { filename: fake_file.filename }), + ); }); });