ソースを参照

Satisfy back-end requirements for ringtone

Add integration tests and organize them by requirements.
master
Allan Crisostomo 3年前
コミット
6814f5e2c5
5個のファイルの変更114行の追加22行の削除
  1. +14
    -5
      REQUIREMENTS.md
  2. +3
    -0
      packages/service-core/jest.setup.ts
  3. +0
    -3
      packages/service-core/src/app.ts
  4. +22
    -0
      packages/service-core/src/modules/ringtone/controller.ts
  5. +75
    -14
      packages/service-core/test/routes/api/ringtones.test.ts

+ 14
- 5
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.

+ 3
- 0
packages/service-core/jest.setup.ts ファイルの表示

@@ -0,0 +1,3 @@
import { config } from 'dotenv'

config()

+ 0
- 3
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<AppOptions> = async (

};

config()

export default app;
export {app};

+ 22
- 0
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)
}
}


+ 75
- 14
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)
})
})
})

読み込み中…
キャンセル
保存