From a9da7baa91ca4828c1765d1671ba8c2d674d2a1d Mon Sep 17 00:00:00 2001 From: El Hilali Mouad Date: Tue, 29 Oct 2024 15:08:35 +0100 Subject: [PATCH 01/11] fix: delete file if exist before uploading another one -I couldn't do this with one query because MySql doesn't support RETURNING clause -For retriveal tests I had to build a clean state for them to work because they were depending on Upload tests --- src/routes/api/tasks/index.ts | 45 +++++++++++++++++----- test/routes/api/tasks/tasks.test.ts | 59 +++++++++++++++++++++++++++-- 2 files changed, 91 insertions(+), 13 deletions(-) diff --git a/src/routes/api/tasks/index.ts b/src/routes/api/tasks/index.ts index 4d7812cf..fff50110 100644 --- a/src/routes/api/tasks/index.ts +++ b/src/routes/api/tasks/index.ts @@ -14,6 +14,7 @@ import { import path from 'node:path' import { pipeline } from 'node:stream/promises' import fs from 'node:fs' +import { FastifyInstance } from 'fastify' const plugin: FastifyPluginAsyncTypebox = async (fastify) => { fastify.get( @@ -246,21 +247,21 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => { const filename = `${id}_${file.filename}` - const affectedRows = await trx('tasks') + const task = await trx('tasks').where({ id }).first() + + if (!task) { + return reply.notFound('Task not found') + } + + await trx('tasks') .where({ id }) .update({ filename }) - if (affectedRows === 0) { - return reply.notFound('Task not found') + if (task.filename) { + await deleteFile(task.filename, fastify) } - const filePath = path.join( - import.meta.dirname, - '../../../..', - fastify.config.UPLOAD_DIRNAME, - fastify.config.UPLOAD_TASKS_DIRNAME, - filename - ) + const filePath = buildFilePath(filename, fastify) await pipeline(file.file, fs.createWriteStream(filePath)) @@ -360,4 +361,28 @@ function isErrnoException (error: unknown): error is NodeJS.ErrnoException { return error instanceof Error && 'code' in error } +function buildFilePath (filename: string, fastify: FastifyInstance) { + return path.join( + import.meta.dirname, + '../../../..', + fastify.config.UPLOAD_DIRNAME, + fastify.config.UPLOAD_TASKS_DIRNAME, + filename + ) +} + +async function deleteFile (fileName: string, fastify: FastifyInstance) { + const filePath = buildFilePath(fileName, fastify) + + try { + await fs.promises.unlink(filePath) + } catch (err) { + if (isErrnoException(err) && err.code === 'ENOENT') { + fastify.log.warn(`File path '${fileName}' not found`) + } else { + throw err + } + } +} + export default plugin diff --git a/test/routes/api/tasks/tasks.test.ts b/test/routes/api/tasks/tasks.test.ts index a22d4777..adb15952 100644 --- a/test/routes/api/tasks/tasks.test.ts +++ b/test/routes/api/tasks/tasks.test.ts @@ -39,7 +39,7 @@ async function uploadImageForTask (app: FastifyInstance, taskId: number, filePat await pipeline(file, writeStream) } -describe('Tasks api (logged user only)', () => { +describe('Tasks api (logged user)', () => { describe('GET /api/tasks', () => { let app: FastifyInstance let userId1: number @@ -509,6 +509,31 @@ describe('Tasks api (logged user only)', () => { assert.strictEqual(message, 'File uploaded successfully') }) + it('should delete existing image for a task before uploading a new one', async (t) => { + app = await build(t) + + await uploadImageForTask(app, taskId, testImagePath, uploadDirTask) + + const { mock: mockUnlink } = t.mock.method(fs.promises, 'unlink') + + const form = new FormData() + form.append('file', fs.createReadStream(testImagePath)) + + const res = await app.injectWithLogin('basic', { + method: 'POST', + url: `/api/tasks/${taskId}/upload`, + payload: form, + headers: form.getHeaders() + }) + + assert.strictEqual(res.statusCode, 200) + + const { message } = JSON.parse(res.payload) + assert.strictEqual(message, 'File uploaded successfully') + + assert.strictEqual(mockUnlink.callCount(), 1) + }) + it('should return 404 if task not found', async (t) => { app = await build(t) @@ -622,6 +647,34 @@ describe('Tasks api (logged user only)', () => { }) describe('Retrieval', () => { + before(async () => { + app = await build() + + await app.knex('tasks').where({ id: taskId }).update({ filename: null }) + + const files = fs.readdirSync(uploadDirTask) + files.forEach((file) => { + const filePath = path.join(uploadDirTask, file) + fs.rmSync(filePath, { recursive: true }) + }) + + await app.close() + }) + + beforeEach(async () => { + app = await build() + await uploadImageForTask(app, taskId, testImagePath, uploadDirTask) + await app.close() + }) + + after(async () => { + const files = fs.readdirSync(uploadDirTask) + files.forEach((file) => { + const filePath = path.join(uploadDirTask, file) + fs.rmSync(filePath, { recursive: true }) + }) + }) + it('should retrieve the uploaded image based on task id and filename', async (t) => { app = await build(t) @@ -737,8 +790,8 @@ describe('Tasks api (logged user only)', () => { it('File deletion transaction should rollback on error', async (t) => { const app = await build(t) - const { mock: mockPipeline } = t.mock.method(fs.promises, 'unlink') - mockPipeline.mockImplementationOnce(() => { + const { mock: mockUnlink } = t.mock.method(fs.promises, 'unlink') + mockUnlink.mockImplementationOnce(() => { return Promise.reject(new Error()) }) From 124ba95b45b51bb8b4f557be034679f34ee1bdb7 Mon Sep 17 00:00:00 2001 From: El Hilali Mouad Date: Tue, 29 Oct 2024 15:18:19 +0100 Subject: [PATCH 02/11] refactor: use deleteFile function in delete endpoint --- src/routes/api/tasks/index.ts | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/src/routes/api/tasks/index.ts b/src/routes/api/tasks/index.ts index fff50110..dafda701 100644 --- a/src/routes/api/tasks/index.ts +++ b/src/routes/api/tasks/index.ts @@ -327,25 +327,7 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => { return reply.notFound(`No task has filename "${filename}"`) } - const filePath = path.join( - import.meta.dirname, - '../../../..', - fastify.config.UPLOAD_DIRNAME, - fastify.config.UPLOAD_TASKS_DIRNAME, - filename - ) - - try { - await fs.promises.unlink(filePath) - } catch (err) { - if (isErrnoException(err) && err.code === 'ENOENT') { - // A file could have been deleted by an external actor, e.g. system administrator. - // We log the error to keep a record of the failure, but consider that the operation was successful. - fastify.log.warn(`File path '${filename}' not found`) - } else { - throw err - } - } + await deleteFile(filename, fastify) reply.code(204) @@ -378,6 +360,8 @@ async function deleteFile (fileName: string, fastify: FastifyInstance) { await fs.promises.unlink(filePath) } catch (err) { if (isErrnoException(err) && err.code === 'ENOENT') { + // A file could have been deleted by an external actor, e.g. system administrator. + // We log the error to keep a record of the failure, but consider that the operation was successful. fastify.log.warn(`File path '${fileName}' not found`) } else { throw err From b5e94d8fa4b6e3455353d735024264ce693e194e Mon Sep 17 00:00:00 2001 From: El Hilali Mouad Date: Wed, 30 Oct 2024 07:44:42 +0100 Subject: [PATCH 03/11] feat: file handling plugin --- src/plugins/custom/file-handling.ts | 58 +++++++++++++++++++++++++++++ src/routes/api/tasks/index.ts | 42 ++------------------- 2 files changed, 61 insertions(+), 39 deletions(-) create mode 100644 src/plugins/custom/file-handling.ts diff --git a/src/plugins/custom/file-handling.ts b/src/plugins/custom/file-handling.ts new file mode 100644 index 00000000..543fb3ec --- /dev/null +++ b/src/plugins/custom/file-handling.ts @@ -0,0 +1,58 @@ +import fp from 'fastify-plugin' +import { BusboyFileStream } from '@fastify/busboy' +import { FastifyInstance } from 'fastify' +import path from 'path' +import { pipeline } from 'node:stream/promises' +import fs from 'node:fs' + +declare module 'fastify' { + export interface FastifyInstance { + fileHandler: { + upload: (fileDir: string, fileName: string, file: BusboyFileStream) => Promise + remove: (fileDir: string, fileName: string) => Promise + } + } +} + +function buildFilePath (fileDir: string, filename: string, fastify: FastifyInstance) { + return path.join( + import.meta.dirname, + '../../../', + fastify.config.UPLOAD_DIRNAME, + fileDir, + filename + ) +} + +async function upload (this: FastifyInstance, fileDir: string, fileName: string, file: BusboyFileStream) { + const filePath = buildFilePath(fileDir, fileName, this) + + await pipeline(file, fs.createWriteStream(filePath)) +} + +async function remove (this: FastifyInstance, fileDir: string, fileName: string) { + const filePath = buildFilePath(fileDir, fileName, this) + + try { + await fs.promises.unlink(filePath) + } catch (err) { + if (isErrnoException(err) && err.code === 'ENOENT') { + // A file could have been deleted by an external actor, e.g. system administrator. + // We log the error to keep a record of the failure, but consider that the operation was successful. + this.log.warn(`File path '${fileName}' not found`) + } else { + throw err + } + } +} + +function isErrnoException (error: unknown): error is NodeJS.ErrnoException { + return error instanceof Error && 'code' in error +} + +export default fp(async (fastify) => { + fastify.decorate('fileHandler', { + upload: upload.bind(fastify), + remove: remove.bind(fastify) + }) +}, { name: 'file-handler' }) diff --git a/src/routes/api/tasks/index.ts b/src/routes/api/tasks/index.ts index dafda701..ea29c723 100644 --- a/src/routes/api/tasks/index.ts +++ b/src/routes/api/tasks/index.ts @@ -12,9 +12,6 @@ import { TaskPaginationResultSchema } from '../../../schemas/tasks.js' import path from 'node:path' -import { pipeline } from 'node:stream/promises' -import fs from 'node:fs' -import { FastifyInstance } from 'fastify' const plugin: FastifyPluginAsyncTypebox = async (fastify) => { fastify.get( @@ -258,13 +255,10 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => { .update({ filename }) if (task.filename) { - await deleteFile(task.filename, fastify) + await fastify.fileHandler.remove(fastify.config.UPLOAD_TASKS_DIRNAME, task.filename) } - const filePath = buildFilePath(filename, fastify) - - await pipeline(file.file, fs.createWriteStream(filePath)) - + await fastify.fileHandler.upload(fastify.config.UPLOAD_TASKS_DIRNAME, filename, file.file) return { message: 'File uploaded successfully' } }).catch(() => { reply.internalServerError('Transaction failed.') @@ -327,7 +321,7 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => { return reply.notFound(`No task has filename "${filename}"`) } - await deleteFile(filename, fastify) + await fastify.fileHandler.remove(fastify.config.UPLOAD_TASKS_DIRNAME, filename) reply.code(204) @@ -339,34 +333,4 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => { ) } -function isErrnoException (error: unknown): error is NodeJS.ErrnoException { - return error instanceof Error && 'code' in error -} - -function buildFilePath (filename: string, fastify: FastifyInstance) { - return path.join( - import.meta.dirname, - '../../../..', - fastify.config.UPLOAD_DIRNAME, - fastify.config.UPLOAD_TASKS_DIRNAME, - filename - ) -} - -async function deleteFile (fileName: string, fastify: FastifyInstance) { - const filePath = buildFilePath(fileName, fastify) - - try { - await fs.promises.unlink(filePath) - } catch (err) { - if (isErrnoException(err) && err.code === 'ENOENT') { - // A file could have been deleted by an external actor, e.g. system administrator. - // We log the error to keep a record of the failure, but consider that the operation was successful. - fastify.log.warn(`File path '${fileName}' not found`) - } else { - throw err - } - } -} - export default plugin From 13236e2052415c1d330fb375b8a7b33cde7bb576 Mon Sep 17 00:00:00 2001 From: El Hilali Mouad Date: Sat, 2 Nov 2024 15:28:28 +0100 Subject: [PATCH 04/11] refactor(file-handling): change paramaters order --- src/plugins/custom/file-handling.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/custom/file-handling.ts b/src/plugins/custom/file-handling.ts index 543fb3ec..1ae756af 100644 --- a/src/plugins/custom/file-handling.ts +++ b/src/plugins/custom/file-handling.ts @@ -14,7 +14,7 @@ declare module 'fastify' { } } -function buildFilePath (fileDir: string, filename: string, fastify: FastifyInstance) { +function buildFilePath (fastify: FastifyInstance, fileDir: string, filename: string) { return path.join( import.meta.dirname, '../../../', @@ -25,13 +25,13 @@ function buildFilePath (fileDir: string, filename: string, fastify: FastifyInsta } async function upload (this: FastifyInstance, fileDir: string, fileName: string, file: BusboyFileStream) { - const filePath = buildFilePath(fileDir, fileName, this) + const filePath = buildFilePath(this, fileName, fileDir) await pipeline(file, fs.createWriteStream(filePath)) } async function remove (this: FastifyInstance, fileDir: string, fileName: string) { - const filePath = buildFilePath(fileDir, fileName, this) + const filePath = buildFilePath(this, fileName, fileDir) try { await fs.promises.unlink(filePath) From bb09fdb26aa841f1df0627370524e368d8f0d5fc Mon Sep 17 00:00:00 2001 From: El Hilali Mouad Date: Sat, 2 Nov 2024 16:18:51 +0100 Subject: [PATCH 05/11] refactor(file-handling): explicit binding by factory & type interface by inference --- src/plugins/custom/file-handling.ts | 57 +++++++++++++++++------------ 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/src/plugins/custom/file-handling.ts b/src/plugins/custom/file-handling.ts index 1ae756af..b80f1680 100644 --- a/src/plugins/custom/file-handling.ts +++ b/src/plugins/custom/file-handling.ts @@ -7,10 +7,7 @@ import fs from 'node:fs' declare module 'fastify' { export interface FastifyInstance { - fileHandler: { - upload: (fileDir: string, fileName: string, file: BusboyFileStream) => Promise - remove: (fileDir: string, fileName: string) => Promise - } + fileHandler: ReturnType } } @@ -24,35 +21,47 @@ function buildFilePath (fastify: FastifyInstance, fileDir: string, filename: str ) } -async function upload (this: FastifyInstance, fileDir: string, fileName: string, file: BusboyFileStream) { - const filePath = buildFilePath(this, fileName, fileDir) +function fileHandlerFactory(fastify: FastifyInstance) { + async function upload (fileDir: string, fileName: string, file: BusboyFileStream) { + console.log('here') - await pipeline(file, fs.createWriteStream(filePath)) -} + const filePath = buildFilePath(fastify, fileDir, fileName) + + console.log(filePath) + + await pipeline(file, fs.createWriteStream(filePath)) + } + + async function remove (fileDir: string, fileName: string) { + const filePath = buildFilePath(fastify, fileDir, fileName) -async function remove (this: FastifyInstance, fileDir: string, fileName: string) { - const filePath = buildFilePath(this, fileName, fileDir) - - try { - await fs.promises.unlink(filePath) - } catch (err) { - if (isErrnoException(err) && err.code === 'ENOENT') { - // A file could have been deleted by an external actor, e.g. system administrator. - // We log the error to keep a record of the failure, but consider that the operation was successful. - this.log.warn(`File path '${fileName}' not found`) - } else { - throw err + try { + await fs.promises.unlink(filePath) + } catch (err) { + if (isErrnoException(err) && err.code === 'ENOENT') { + // A file could have been deleted by an external actor, e.g. system administrator. + // We log the error to keep a record of the failure, but consider that the operation was successful. + fastify.log.warn(`File path '${fileName}' not found`) + } else { + throw err + } } } + + return { + upload, + remove + } } + + function isErrnoException (error: unknown): error is NodeJS.ErrnoException { return error instanceof Error && 'code' in error } export default fp(async (fastify) => { - fastify.decorate('fileHandler', { - upload: upload.bind(fastify), - remove: remove.bind(fastify) - }) + + + fastify.decorate('fileHandler', fileHandlerFactory(fastify)) }, { name: 'file-handler' }) From 823cc80604ca80c3ae1b0218da1b4d3d9edb74a7 Mon Sep 17 00:00:00 2001 From: El Hilali Mouad Date: Sat, 2 Nov 2024 16:21:13 +0100 Subject: [PATCH 06/11] chore: lint\ --- src/plugins/custom/file-handling.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/plugins/custom/file-handling.ts b/src/plugins/custom/file-handling.ts index b80f1680..d928183a 100644 --- a/src/plugins/custom/file-handling.ts +++ b/src/plugins/custom/file-handling.ts @@ -21,7 +21,7 @@ function buildFilePath (fastify: FastifyInstance, fileDir: string, filename: str ) } -function fileHandlerFactory(fastify: FastifyInstance) { +function fileHandlerFactory (fastify: FastifyInstance) { async function upload (fileDir: string, fileName: string, file: BusboyFileStream) { console.log('here') @@ -54,14 +54,10 @@ function fileHandlerFactory(fastify: FastifyInstance) { } } - - function isErrnoException (error: unknown): error is NodeJS.ErrnoException { return error instanceof Error && 'code' in error } export default fp(async (fastify) => { - - fastify.decorate('fileHandler', fileHandlerFactory(fastify)) }, { name: 'file-handler' }) From d84045854713fe7174bec3a96b46da944d309087 Mon Sep 17 00:00:00 2001 From: El Hilali Mouad Date: Sat, 2 Nov 2024 16:35:14 +0100 Subject: [PATCH 07/11] refactor(tasks test): clear dir function for reuse --- src/plugins/custom/file-handling.ts | 2 -- test/routes/api/tasks/tasks.test.ts | 40 ++++++++--------------------- 2 files changed, 11 insertions(+), 31 deletions(-) diff --git a/src/plugins/custom/file-handling.ts b/src/plugins/custom/file-handling.ts index d928183a..71b678de 100644 --- a/src/plugins/custom/file-handling.ts +++ b/src/plugins/custom/file-handling.ts @@ -23,8 +23,6 @@ function buildFilePath (fastify: FastifyInstance, fileDir: string, filename: str function fileHandlerFactory (fastify: FastifyInstance) { async function upload (fileDir: string, fileName: string, file: BusboyFileStream) { - console.log('here') - const filePath = buildFilePath(fastify, fileDir, fileName) console.log(filePath) diff --git a/test/routes/api/tasks/tasks.test.ts b/test/routes/api/tasks/tasks.test.ts index adb15952..fc6b83d5 100644 --- a/test/routes/api/tasks/tasks.test.ts +++ b/test/routes/api/tasks/tasks.test.ts @@ -470,11 +470,7 @@ describe('Tasks api (logged user)', () => { }) after(async () => { - const files = fs.readdirSync(uploadDirTask) - files.forEach((file) => { - const filePath = path.join(uploadDirTask, file) - fs.rmSync(filePath, { recursive: true }) - }) + clearDir(uploadDir) await app.close() }) @@ -652,12 +648,6 @@ describe('Tasks api (logged user)', () => { await app.knex('tasks').where({ id: taskId }).update({ filename: null }) - const files = fs.readdirSync(uploadDirTask) - files.forEach((file) => { - const filePath = path.join(uploadDirTask, file) - fs.rmSync(filePath, { recursive: true }) - }) - await app.close() }) @@ -668,11 +658,7 @@ describe('Tasks api (logged user)', () => { }) after(async () => { - const files = fs.readdirSync(uploadDirTask) - files.forEach((file) => { - const filePath = path.join(uploadDirTask, file) - fs.rmSync(filePath, { recursive: true }) - }) + clearDir(uploadDirTask) }) it('should retrieve the uploaded image based on task id and filename', async (t) => { @@ -709,15 +695,7 @@ describe('Tasks api (logged user)', () => { describe('Deletion', () => { before(async () => { app = await build() - await app.knex('tasks').where({ id: taskId }).update({ filename: null }) - - const files = fs.readdirSync(uploadDirTask) - files.forEach((file) => { - const filePath = path.join(uploadDirTask, file) - fs.rmSync(filePath, { recursive: true }) - }) - await app.close() }) @@ -728,11 +706,7 @@ describe('Tasks api (logged user)', () => { }) after(async () => { - const files = fs.readdirSync(uploadDirTask) - files.forEach((file) => { - const filePath = path.join(uploadDirTask, file) - fs.rmSync(filePath, { recursive: true }) - }) + clearDir(uploadDirTask) }) it('should remove an existing image for a task', async (t) => { @@ -815,3 +789,11 @@ describe('Tasks api (logged user)', () => { }) }) }) + +function clearDir (dirPath: string) { + const files = fs.readdirSync(dirPath) + files.forEach((file) => { + const filePath = path.join(dirPath, file) + fs.rmSync(filePath, { recursive: true }) + }) +} From a5ffeb47162fadd1b1904d1d00d161714d0a3aca Mon Sep 17 00:00:00 2001 From: El Hilali Mouad Date: Sat, 2 Nov 2024 16:37:45 +0100 Subject: [PATCH 08/11] chore: remove console.log --- src/plugins/custom/file-handling.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/plugins/custom/file-handling.ts b/src/plugins/custom/file-handling.ts index 71b678de..dad96b97 100644 --- a/src/plugins/custom/file-handling.ts +++ b/src/plugins/custom/file-handling.ts @@ -25,8 +25,6 @@ function fileHandlerFactory (fastify: FastifyInstance) { async function upload (fileDir: string, fileName: string, file: BusboyFileStream) { const filePath = buildFilePath(fastify, fileDir, fileName) - console.log(filePath) - await pipeline(file, fs.createWriteStream(filePath)) } From 18f39884efb164975e0c0e482bbb5032fbb30684 Mon Sep 17 00:00:00 2001 From: El Hilali Mouad Date: Sun, 3 Nov 2024 07:48:50 +0100 Subject: [PATCH 09/11] refactor(tasks tests): move setup & tear down from child `describe` to parent `describe` --- .gitignore | 3 ++ test/routes/api/tasks/tasks.test.ts | 44 ++++++----------------------- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index 2b923efe..15bc41df 100644 --- a/.gitignore +++ b/.gitignore @@ -140,3 +140,6 @@ yarn.lock # uploaded files uploads/tasks/* + +# sonnar locally +.scannerwork diff --git a/test/routes/api/tasks/tasks.test.ts b/test/routes/api/tasks/tasks.test.ts index fc6b83d5..86e96694 100644 --- a/test/routes/api/tasks/tasks.test.ts +++ b/test/routes/api/tasks/tasks.test.ts @@ -348,7 +348,7 @@ describe('Tasks api (logged user)', () => { const res = await app.injectWithLogin('admin', { method: 'DELETE', - url: '/api/tasks/9999' + url: '/api/tasks/1234' }) assert.strictEqual(res.statusCode, 404) @@ -466,9 +466,17 @@ describe('Tasks api (logged user)', () => { status: TaskStatusEnum.New }) + await app.knex('tasks').where({ id: taskId }).update({ filename: null }) + app.close() }) + beforeEach(async () => { + app = await build() + await uploadImageForTask(app, taskId, testImagePath, uploadDirTask) + await app.close() + }) + after(async () => { clearDir(uploadDir) @@ -643,24 +651,6 @@ describe('Tasks api (logged user)', () => { }) describe('Retrieval', () => { - before(async () => { - app = await build() - - await app.knex('tasks').where({ id: taskId }).update({ filename: null }) - - await app.close() - }) - - beforeEach(async () => { - app = await build() - await uploadImageForTask(app, taskId, testImagePath, uploadDirTask) - await app.close() - }) - - after(async () => { - clearDir(uploadDirTask) - }) - it('should retrieve the uploaded image based on task id and filename', async (t) => { app = await build(t) @@ -693,22 +683,6 @@ describe('Tasks api (logged user)', () => { }) describe('Deletion', () => { - before(async () => { - app = await build() - await app.knex('tasks').where({ id: taskId }).update({ filename: null }) - await app.close() - }) - - beforeEach(async () => { - app = await build() - await uploadImageForTask(app, taskId, testImagePath, uploadDirTask) - await app.close() - }) - - after(async () => { - clearDir(uploadDirTask) - }) - it('should remove an existing image for a task', async (t) => { app = await build(t) From 137af76249932e59f11ed6aa75350899ec690940 Mon Sep 17 00:00:00 2001 From: El Hilali Mouad Date: Sun, 3 Nov 2024 12:38:21 +0100 Subject: [PATCH 10/11] refactor(tasks test) - Generalize setup and tear down for all tasks image tests describe blocks - In replacing image upload test, assert that the old file is really removed and the new file is persisted --- src/plugins/custom/file-handling.ts | 4 +- src/routes/api/tasks/index.ts | 2 +- .../api/tasks/fixtures/short-logo-copy.png | Bin 0 -> 20109 bytes test/routes/api/tasks/tasks.test.ts | 37 ++++++++++++------ 4 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 test/routes/api/tasks/fixtures/short-logo-copy.png diff --git a/src/plugins/custom/file-handling.ts b/src/plugins/custom/file-handling.ts index dad96b97..c5b22c44 100644 --- a/src/plugins/custom/file-handling.ts +++ b/src/plugins/custom/file-handling.ts @@ -21,7 +21,7 @@ function buildFilePath (fastify: FastifyInstance, fileDir: string, filename: str ) } -function fileHandlerFactory (fastify: FastifyInstance) { +function createFileHandler (fastify: FastifyInstance) { async function upload (fileDir: string, fileName: string, file: BusboyFileStream) { const filePath = buildFilePath(fastify, fileDir, fileName) @@ -55,5 +55,5 @@ function isErrnoException (error: unknown): error is NodeJS.ErrnoException { } export default fp(async (fastify) => { - fastify.decorate('fileHandler', fileHandlerFactory(fastify)) + fastify.decorate('fileHandler', createFileHandler(fastify)) }, { name: 'file-handler' }) diff --git a/src/routes/api/tasks/index.ts b/src/routes/api/tasks/index.ts index ea29c723..9c90ed12 100644 --- a/src/routes/api/tasks/index.ts +++ b/src/routes/api/tasks/index.ts @@ -244,7 +244,7 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => { const filename = `${id}_${file.filename}` - const task = await trx('tasks').where({ id }).first() + const task = await trx('tasks').select('filename').where({ id }).first() if (!task) { return reply.notFound('Task not found') diff --git a/test/routes/api/tasks/fixtures/short-logo-copy.png b/test/routes/api/tasks/fixtures/short-logo-copy.png new file mode 100644 index 0000000000000000000000000000000000000000..8041e33dd617054d1f796032f58c364fec3732cf GIT binary patch literal 20109 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 a=l!lP;&eQ*_$(*@qN%QPHusFh-Tw>VhJ3#O literal 0 HcmV?d00001 diff --git a/test/routes/api/tasks/tasks.test.ts b/test/routes/api/tasks/tasks.test.ts index 86e96694..9ea7cddd 100644 --- a/test/routes/api/tasks/tasks.test.ts +++ b/test/routes/api/tasks/tasks.test.ts @@ -1,4 +1,4 @@ -import { after, before, beforeEach, describe, it } from 'node:test' +import { after, afterEach, before, beforeEach, describe, it } from 'node:test' import assert from 'node:assert' import { build } from '../../../helper.js' import { @@ -460,30 +460,34 @@ describe('Tasks api (logged user)', () => { uploadDirTask = path.join(uploadDir, app.config.UPLOAD_TASKS_DIRNAME) assert.ok(fs.existsSync(uploadDir)) + clearDir(uploadDirTask) + + app.close() + }) + + beforeEach(async (t) => { + app = await build() taskId = await createTask(app, { name: 'Task with image', author_id: 1, status: TaskStatusEnum.New }) - - await app.knex('tasks').where({ id: taskId }).update({ filename: null }) - - app.close() + await uploadImageForTask(app, taskId, testImagePath, uploadDirTask) + await app.close() }) - beforeEach(async () => { + afterEach(async () => { app = await build() - await uploadImageForTask(app, taskId, testImagePath, uploadDirTask) + await app.knex('tasks').where({ id: taskId }).update({ filename: null }) + clearDir(uploadDirTask) await app.close() }) after(async () => { - clearDir(uploadDir) - await app.close() }) - describe('Upload', () => { + describe('Upload', (t) => { it('should create upload directories at boot if not exist', async (t) => { fs.rmSync(uploadDir, { recursive: true }) assert.ok(!fs.existsSync(uploadDir)) @@ -516,12 +520,14 @@ describe('Tasks api (logged user)', () => { it('should delete existing image for a task before uploading a new one', async (t) => { app = await build(t) - await uploadImageForTask(app, taskId, testImagePath, uploadDirTask) - const { mock: mockUnlink } = t.mock.method(fs.promises, 'unlink') + const newTestImageFileName = 'short-logo-copy.png' + + const newTestImagePath = path.join(fixturesDir, newTestImageFileName) + const form = new FormData() - form.append('file', fs.createReadStream(testImagePath)) + form.append('file', fs.createReadStream(newTestImagePath)) const res = await app.injectWithLogin('basic', { method: 'POST', @@ -536,6 +542,11 @@ describe('Tasks api (logged user)', () => { assert.strictEqual(message, 'File uploaded successfully') assert.strictEqual(mockUnlink.callCount(), 1) + + + const files = fs.readdirSync(uploadDirTask) + assert.strictEqual(files.length, 1) + assert.strictEqual(files[0], `${taskId}_${newTestImageFileName}`) }) it('should return 404 if task not found', async (t) => { From 87876631e52db2c4ec779b4693b0aa280a55d5e2 Mon Sep 17 00:00:00 2001 From: El Hilali Mouad Date: Sun, 3 Nov 2024 12:49:30 +0100 Subject: [PATCH 11/11] fix: fileHandler type & lint --- src/plugins/custom/file-handling.ts | 2 +- test/routes/api/tasks/tasks.test.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/plugins/custom/file-handling.ts b/src/plugins/custom/file-handling.ts index c5b22c44..84a5f3ae 100644 --- a/src/plugins/custom/file-handling.ts +++ b/src/plugins/custom/file-handling.ts @@ -7,7 +7,7 @@ import fs from 'node:fs' declare module 'fastify' { export interface FastifyInstance { - fileHandler: ReturnType + fileHandler: ReturnType } } diff --git a/test/routes/api/tasks/tasks.test.ts b/test/routes/api/tasks/tasks.test.ts index 9ea7cddd..5ff1684e 100644 --- a/test/routes/api/tasks/tasks.test.ts +++ b/test/routes/api/tasks/tasks.test.ts @@ -543,7 +543,6 @@ describe('Tasks api (logged user)', () => { assert.strictEqual(mockUnlink.callCount(), 1) - const files = fs.readdirSync(uploadDirTask) assert.strictEqual(files.length, 1) assert.strictEqual(files[0], `${taskId}_${newTestImageFileName}`)