diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index 8815bc2..c167e99 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -18,14 +18,23 @@ - [ ] In the front-end, the client requests provides a search keyword to request for composers and ringtones from the back-end. - [ ] In the back-end, the server retrieves the composers and ringtones whose name matches the search keyword provided by the front-end. +- As a client, I want to browse ringtones. + - [ ] In the front-end, the client provides an optional skip and take arguments to request multiple ringtones from the back-end. + - [X] In the back-end, the server sends the ringtones to the front-end. - As a composer, I want to create a ringtone. - [ ] In the front-end, the client inputs ringtone data to the view. - [ ] In the front-end, the client sends the ringtone data to the back-end. - - [ ] In the back-end, the server stores the ringtone. + - [X] In the back-end, the server stores the ringtone data. - As a composer, I want to update a ringtone. - [ ] In the front-end, the client modifies the ringtone data retrieved from the back-end and loaded on the view. - [ ] In the front-end, the client sends the ringtone data to the back-end. - - [ ] In the back-end, the server stores the ringtone. -- As a composer, I want to delete a ringtone. - - [ ] In the front-end, the client provides a ringtone ID to request a ringtone's deletion to the back-end. - - [ ] In the back-end, the server deletes the ringtone if its ID matches the one provided by the front-end. + - [X] In the back-end, the server stores the updated ringtone data. +- As a composer, I want to soft-delete a ringtone. + - [ ] In the front-end, the client provides a ringtone ID to request a ringtone's soft deletion to the back-end. + - [X] In the back-end, the server tags a ringtone as deleted if its ID matches the one provided by the front-end. +- As a composer, I want to undo deletion of a soft-deleted ringtone. + - [ ] In the front-end, the client provides a ringtone ID to request a ringtone's deletion rollback to the back-end. + - [X] In the back-end, the server untags a ringtone as deleted if its ID matches the one provided by the front-end. +- As a composer, I want to hard-delete of a ringtone. + - [ ] In the front-end, the client provides a ringtone ID to request a ringtone's deletion rollback to the back-end. + - [X] In the back-end, the server removes a ringtone in the database if its ID matches the one provided by the front-end. diff --git a/packages/service-core/jest.setup.ts b/packages/service-core/jest.setup.ts index e69de29..5b5887c 100644 --- a/packages/service-core/jest.setup.ts +++ b/packages/service-core/jest.setup.ts @@ -0,0 +1,3 @@ +import { config } from 'dotenv' + +config() diff --git a/packages/service-core/src/app.ts b/packages/service-core/src/app.ts index 16f6cb7..fd53bc1 100644 --- a/packages/service-core/src/app.ts +++ b/packages/service-core/src/app.ts @@ -1,5 +1,4 @@ import '@abraham/reflection' -import { config } from 'dotenv' import {join} from 'path' import AutoLoad, {AutoloadPluginOptions} from 'fastify-autoload' @@ -34,7 +33,5 @@ const app: FastifyPluginAsync = async ( }; -config() - export default app; export {app}; diff --git a/packages/service-core/src/modules/ringtone/controller.ts b/packages/service-core/src/modules/ringtone/controller.ts index ee77f0e..c9008bc 100644 --- a/packages/service-core/src/modules/ringtone/controller.ts +++ b/packages/service-core/src/modules/ringtone/controller.ts @@ -14,17 +14,21 @@ export class RingtoneController { try { const data = await this.ringtoneService.get(request.params['id']) if (typeof (data.deletedAt as Date) !== 'undefined') { + reply.raw.statusMessage = 'Ringtone Deleted Previously' reply.gone() return } if (!data) { + reply.raw.statusMessage = 'Ringtone Not Found' reply.notFound() return } + reply.raw.statusMessage = 'Single Ringtone Retrieved' return { data, } } catch (err) { + reply.raw.statusMessage = 'Get Ringtone Error' reply.internalServerError(err.message) } } @@ -39,12 +43,14 @@ export class RingtoneController { const skip = !isNaN(skipNumber) ? skipNumber : undefined const take = !isNaN(takeNumber) ? takeNumber : undefined + reply.raw.statusMessage = 'Multiple Ringtones Retrieved' return { data: await this.ringtoneService.browse(skip, take), skip, take, } } catch (err) { + reply.raw.statusMessage = 'Browse Ringtones Error' reply.internalServerError(err.message) } } @@ -52,11 +58,13 @@ export class RingtoneController { search = async (request: any, reply: any) => { try { const { 'q': query } = request.query + reply.raw.statusMessage = 'Search Results Retrieved' return { data: await this.ringtoneService.search(query), query, } } catch (err) { + reply.raw.statusMessage = 'Search Error' reply.internalServerError(err.message) } } @@ -65,10 +73,12 @@ export class RingtoneController { try { const data = await this.ringtoneService.create(request.body) reply.status(201) + reply.raw.statusMessage = 'Ringtone Created' return { data, } } catch (err) { + reply.raw.statusMessage = 'Create Ringtone Error' reply.internalServerError(err.message) } } @@ -81,13 +91,16 @@ export class RingtoneController { id: request.params['id'], }) if (data.deletedAt !== null) { + reply.raw.statusMessage = 'Ringtone Deleted Previously' reply.gone() return } if (!data) { + reply.raw.statusMessage = 'Ringtone Not Found' reply.notFound() return } + reply.raw.statusMessage = 'Ringtone Updated' return { data, } @@ -101,13 +114,16 @@ export class RingtoneController { // TODO validate data const data = await this.ringtoneService.softDelete(request.params['id']) if (!data) { + reply.raw.statusMessage = 'Ringtone Not Found' reply.notFound() return } + reply.raw.statusMessage = 'Ringtone Soft-Deleted' return { data, } } catch (err) { + reply.raw.statusMessage = 'Soft-Delete Ringtone Error' reply.internalServerError(err.message) } } @@ -117,13 +133,16 @@ export class RingtoneController { // TODO validate data const data = await this.ringtoneService.undoDelete(request.params['id']) if (!data) { + reply.raw.statusMessage = 'Ringtone Not Found' reply.notFound() return } + reply.raw.statusMessage = 'Ringtone Restored' return { data, } } catch (err) { + reply.raw.statusMessage = 'Restore Ringtone Error' reply.internalServerError(err.message) } } @@ -133,10 +152,13 @@ export class RingtoneController { // TODO validate data await this.ringtoneService.hardDelete(request.params['id']) reply.status(204) + reply.raw.statusMessage = 'Ringtone Hard-Deleted' } catch (err) { if (err instanceof DoubleDeletionError) { + reply.raw.statusMessage = 'Ringtone Not Found' reply.notFound(err.message) } + reply.raw.statusMessage = 'Delete Ringtone Error' reply.internalServerError(err.message) } } diff --git a/packages/service-core/test/routes/api/ringtones.test.ts b/packages/service-core/test/routes/api/ringtones.test.ts index ad5f1c7..84d5031 100644 --- a/packages/service-core/test/routes/api/ringtones.test.ts +++ b/packages/service-core/test/routes/api/ringtones.test.ts @@ -3,7 +3,7 @@ import {build} from '../../helper' import ringtoneModule from '../../../src/modules/ringtone' import MockRingtoneRepository from '../../mocks/repositories/Ringtone' -describe('ringtone resource', () => { +describe('ringtone', () => { let app: FastifyInstance beforeEach(async () => { @@ -44,8 +44,14 @@ describe('ringtone resource', () => { await app.close() }) - describe('collection', () => { - it('should be browsable', async () => { + describe('on searching', () => { + it('should send the data to the front-end', async () => { + // TODO + }) + }) + + describe('on browsing', () => { + it('should send the data to the front-end', async () => { const res = await app.inject({ url: '/api/ringtones', method: 'GET', @@ -53,12 +59,10 @@ describe('ringtone resource', () => { const parsedPayload = JSON.parse(res.payload) expect(Array.isArray(parsedPayload.data)).toBe(true) }) + }) - it('should be searchable', async () => { - // TODO - }) - - it('should be extendable', async () => { + describe('on creation', () => { + it('should store the data', async () => { const res = await app.inject({ url: '/api/ringtones', method: 'POST', @@ -72,16 +76,73 @@ describe('ringtone resource', () => { }) }) const parsedPayload = JSON.parse(res.payload) - expect(parsedPayload.data).toEqual({ - id: expect.any(String), + expect(parsedPayload.data).toEqual(expect.objectContaining({ name: 'New Ringtone', data: '4c4', - createdAt: expect.any(String), - updatedAt: expect.any(String), - deletedAt: null, composerId: '00000000-0000-0000-000000000000', + })) + }) + }) + + describe('on updating', () => { + it('should store the updated data', async () => { + const res = await app.inject({ + url: '/api/ringtones/00000000-0000-0000-000000000000', + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + }, + payload: JSON.stringify({ + name: 'Updated Ringtone', + data: '4c8', + composerId: '00000000-0000-0000-000000000000', + }) + }) + const parsedPayload = JSON.parse(res.payload) + expect(parsedPayload.data).toEqual(expect.objectContaining({ + name: 'Updated Ringtone', + data: '4c8', + composerId: '00000000-0000-0000-000000000000', + })) + expect(parsedPayload.data.createdAt).not.toEqual(parsedPayload.data.updatedAt) + }) + }) + + describe('on soft deletion', () => { + it('should be tagged as deleted', async () => { + const res = await app.inject({ + url: '/api/ringtones/00000000-0000-0000-000000000000/delete', + method: 'POST', + }) + const parsedPayload = JSON.parse(res.payload) + expect(parsedPayload.data).toEqual(expect.objectContaining({ + id: '00000000-0000-0000-000000000000', + deletedAt: expect.any(String), + })) + }) + }) + + describe('on undoing deletion', () => { + it('should be untagged as deleted', async () => { + const res = await app.inject({ + url: '/api/ringtones/00000000-0000-0000-000000000000/delete', + method: 'DELETE', + }) + const parsedPayload = JSON.parse(res.payload) + expect(parsedPayload.data).toEqual(expect.objectContaining({ + id: '00000000-0000-0000-000000000000', + deletedAt: null, + })) + }) + }) + + describe('on hard deletion', () => { + it('should be removed', async () => { + const res = await app.inject({ + url: '/api/ringtones/00000000-0000-0000-000000000000', + method: 'DELETE', }) - console.log(parsedPayload) + expect(res.statusCode).toBe(204) }) }) })