|
@@ -7,23 +7,12 @@ import { |
|
|
expect, |
|
|
expect, |
|
|
it, vi, |
|
|
it, vi, |
|
|
} from 'vitest'; |
|
|
} from 'vitest'; |
|
|
import { |
|
|
|
|
|
tmpdir |
|
|
|
|
|
} from 'os'; |
|
|
|
|
|
import { |
|
|
|
|
|
mkdtemp, |
|
|
|
|
|
rm, |
|
|
|
|
|
writeFile, |
|
|
|
|
|
} from 'fs/promises'; |
|
|
|
|
|
import { |
|
|
|
|
|
join |
|
|
|
|
|
} from 'path'; |
|
|
|
|
|
import {request} from 'http'; |
|
|
import {request} from 'http'; |
|
|
import {constants} from 'http2'; |
|
|
import {constants} from 'http2'; |
|
|
import {Backend} from '../../../src/backend'; |
|
|
import {Backend} from '../../../src/backend'; |
|
|
import { application, resource, validation as v, Resource } from '../../../src/common'; |
|
|
|
|
|
|
|
|
import {application, resource, validation as v, Resource, Application} from '../../../src/common'; |
|
|
import { autoIncrement } from '../../fixtures'; |
|
|
import { autoIncrement } from '../../fixtures'; |
|
|
import { createTestClient, TestClient, DummyDataSource } from '../../utils'; |
|
|
|
|
|
|
|
|
import {createTestClient, TestClient, DummyDataSource, DummyError} from '../../utils'; |
|
|
import {DataSource} from '../../../src/backend/data-source'; |
|
|
import {DataSource} from '../../../src/backend/data-source'; |
|
|
|
|
|
|
|
|
const PORT = 4001; |
|
|
const PORT = 4001; |
|
@@ -36,8 +25,44 @@ const CONTENT_TYPE_CHARSET = 'utf-8'; |
|
|
const CONTENT_TYPE = ACCEPT; |
|
|
const CONTENT_TYPE = ACCEPT; |
|
|
|
|
|
|
|
|
describe('error handling', () => { |
|
|
describe('error handling', () => { |
|
|
|
|
|
let Piano: Resource; |
|
|
|
|
|
let app: Application; |
|
|
|
|
|
let dataSource: DataSource; |
|
|
|
|
|
let backend: Backend; |
|
|
|
|
|
let server: ReturnType<Backend['createHttpServer']>; |
|
|
let client: TestClient; |
|
|
let client: TestClient; |
|
|
beforeEach(() => { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
beforeAll(() => { |
|
|
|
|
|
Piano = resource(v.object( |
|
|
|
|
|
{ |
|
|
|
|
|
brand: v.string() |
|
|
|
|
|
}, |
|
|
|
|
|
v.never() |
|
|
|
|
|
)) |
|
|
|
|
|
.name('Piano' as const) |
|
|
|
|
|
.route('pianos' as const) |
|
|
|
|
|
.id('id' as const, { |
|
|
|
|
|
generationStrategy: autoIncrement, |
|
|
|
|
|
serialize: (id) => id?.toString() ?? '0', |
|
|
|
|
|
deserialize: (id) => Number.isFinite(Number(id)) ? Number(id) : 0, |
|
|
|
|
|
schema: v.number(), |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
app = application({ |
|
|
|
|
|
name: 'piano-service', |
|
|
|
|
|
}) |
|
|
|
|
|
.resource(Piano); |
|
|
|
|
|
|
|
|
|
|
|
dataSource = new DummyDataSource(); |
|
|
|
|
|
|
|
|
|
|
|
backend = app.createBackend({ |
|
|
|
|
|
dataSource, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
server = backend.createHttpServer({ |
|
|
|
|
|
basePath: BASE_PATH |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
client = createTestClient({ |
|
|
client = createTestClient({ |
|
|
host: HOST, |
|
|
host: HOST, |
|
|
port: PORT, |
|
|
port: PORT, |
|
@@ -47,382 +72,329 @@ describe('error handling', () => { |
|
|
.acceptCharset(ACCEPT_CHARSET) |
|
|
.acceptCharset(ACCEPT_CHARSET) |
|
|
.contentType(CONTENT_TYPE) |
|
|
.contentType(CONTENT_TYPE) |
|
|
.contentCharset(CONTENT_TYPE_CHARSET); |
|
|
.contentCharset(CONTENT_TYPE_CHARSET); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
describe('on internal errors', () => { |
|
|
|
|
|
let baseDir: string; |
|
|
|
|
|
beforeAll(async () => { |
|
|
|
|
|
try { |
|
|
|
|
|
baseDir = await mkdtemp(join(tmpdir(), 'yasumi-')); |
|
|
|
|
|
} catch { |
|
|
|
|
|
// noop |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
afterAll(async () => { |
|
|
|
|
|
try { |
|
|
|
|
|
await rm(baseDir, { |
|
|
|
|
|
recursive: true, |
|
|
|
|
|
}); |
|
|
|
|
|
} catch { |
|
|
|
|
|
// noop |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
let Piano: Resource; |
|
|
|
|
|
beforeAll(() => { |
|
|
|
|
|
Piano = resource(v.object( |
|
|
|
|
|
{ |
|
|
|
|
|
brand: v.string() |
|
|
|
|
|
}, |
|
|
|
|
|
v.never() |
|
|
|
|
|
)) |
|
|
|
|
|
.name('Piano' as const) |
|
|
|
|
|
.route('pianos' as const) |
|
|
|
|
|
.id('id' as const, { |
|
|
|
|
|
generationStrategy: autoIncrement, |
|
|
|
|
|
serialize: (id) => id?.toString() ?? '0', |
|
|
|
|
|
deserialize: (id) => Number.isFinite(Number(id)) ? Number(id) : 0, |
|
|
|
|
|
schema: v.number(), |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
let dataSource: DataSource; |
|
|
|
|
|
let backend: Backend; |
|
|
|
|
|
let server: ReturnType<Backend['createHttpServer']>; |
|
|
|
|
|
beforeEach(() => { |
|
|
|
|
|
const app = application({ |
|
|
|
|
|
name: 'piano-service', |
|
|
|
|
|
}) |
|
|
|
|
|
.resource(Piano); |
|
|
|
|
|
|
|
|
|
|
|
dataSource = new DummyDataSource(); |
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
|
|
server.on('error', (err) => { |
|
|
|
|
|
reject(err); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
backend = app.createBackend({ |
|
|
|
|
|
dataSource, |
|
|
|
|
|
|
|
|
server.on('listening', () => { |
|
|
|
|
|
resolve(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
server = backend.createHttpServer({ |
|
|
|
|
|
basePath: BASE_PATH |
|
|
|
|
|
|
|
|
server.listen({ |
|
|
|
|
|
port: PORT |
|
|
}); |
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
|
|
server.on('error', (err) => { |
|
|
|
|
|
reject(err); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
afterAll(() => new Promise<void>((resolve, reject) => { |
|
|
|
|
|
server.close((err) => { |
|
|
|
|
|
if (err) { |
|
|
|
|
|
reject(err); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
server.on('listening', () => { |
|
|
|
|
|
resolve(); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
resolve(); |
|
|
|
|
|
}); |
|
|
|
|
|
})); |
|
|
|
|
|
|
|
|
server.listen({ |
|
|
|
|
|
port: PORT |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
describe('serving collections', () => { |
|
|
|
|
|
beforeEach(() => { |
|
|
|
|
|
Piano.canFetchCollection(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
afterEach(() => new Promise((resolve, reject) => { |
|
|
|
|
|
server.close((err) => { |
|
|
|
|
|
if (err) { |
|
|
|
|
|
reject(err); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
afterEach(() => { |
|
|
|
|
|
Piano.canFetchCollection(false); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
resolve(); |
|
|
|
|
|
}); |
|
|
|
|
|
})); |
|
|
|
|
|
|
|
|
|
|
|
describe.skip('serving collections', () => { |
|
|
|
|
|
beforeEach(() => { |
|
|
|
|
|
Piano.canFetchCollection(); |
|
|
|
|
|
return new Promise((resolve) => { |
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
resolve(); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on query', async () => { |
|
|
|
|
|
vi |
|
|
|
|
|
.spyOn(DummyDataSource.prototype, 'getMultiple') |
|
|
|
|
|
.mockImplementationOnce(() => { throw new DummyError() }); |
|
|
|
|
|
|
|
|
afterEach(() => { |
|
|
|
|
|
Piano.canFetchCollection(false); |
|
|
|
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'GET', |
|
|
|
|
|
path: `${BASE_PATH}/pianos`, |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on query', async () => { |
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'GET', |
|
|
|
|
|
path: `${BASE_PATH}/pianos`, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Unable To Fetch Piano Collection'); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
it('throws on HEAD method', async () => { |
|
|
|
|
|
vi |
|
|
|
|
|
.spyOn(DummyDataSource.prototype, 'getMultiple') |
|
|
|
|
|
.mockImplementationOnce(() => { throw new DummyError() }); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Unable To Fetch Piano Collection'); |
|
|
|
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'HEAD', |
|
|
|
|
|
path: `${BASE_PATH}/pianos`, |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on HEAD method', async () => { |
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'HEAD', |
|
|
|
|
|
path: `${BASE_PATH}/pianos`, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Unable To Fetch Piano Collection'); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Unable To Fetch Piano Collection'); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
describe('serving items', () => { |
|
|
|
|
|
beforeEach(() => { |
|
|
|
|
|
Piano.canFetchItem(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
describe('serving items', () => { |
|
|
|
|
|
const data = { |
|
|
|
|
|
id: 1, |
|
|
|
|
|
brand: 'Yamaha' |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
afterEach(() => { |
|
|
|
|
|
Piano.canFetchItem(false); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
beforeEach(async () => { |
|
|
|
|
|
const resourcePath = join(baseDir, 'pianos.jsonl'); |
|
|
|
|
|
await writeFile(resourcePath, JSON.stringify(data)); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on query', async () => { |
|
|
|
|
|
vi |
|
|
|
|
|
.spyOn(DummyDataSource.prototype, 'getById') |
|
|
|
|
|
.mockImplementationOnce(() => { throw new DummyError() }); |
|
|
|
|
|
|
|
|
beforeEach(() => { |
|
|
|
|
|
Piano.canFetchItem(); |
|
|
|
|
|
return new Promise((resolve) => { |
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
resolve(); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'GET', |
|
|
|
|
|
path: `${BASE_PATH}/pianos/2`, |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
afterEach(() => { |
|
|
|
|
|
Piano.canFetchItem(false); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Unable To Fetch Piano'); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on query', async () => { |
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'GET', |
|
|
|
|
|
path: `${BASE_PATH}/pianos/2`, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on HEAD method', async () => { |
|
|
|
|
|
vi |
|
|
|
|
|
.spyOn(DummyDataSource.prototype, 'getById') |
|
|
|
|
|
.mockImplementationOnce(() => { throw new DummyError() }); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Unable To Fetch Piano'); |
|
|
|
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'HEAD', |
|
|
|
|
|
path: `${BASE_PATH}/pianos/2`, |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on HEAD method', async () => { |
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'HEAD', |
|
|
|
|
|
path: `${BASE_PATH}/pianos/2`, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Unable To Fetch Piano'); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
it('throws on item not found', async () => { |
|
|
|
|
|
vi |
|
|
|
|
|
.spyOn(DummyDataSource.prototype, 'getById') |
|
|
|
|
|
.mockResolvedValueOnce(null as never); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Unable To Fetch Piano'); |
|
|
|
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'GET', |
|
|
|
|
|
path: `${BASE_PATH}/pianos/2`, |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on item not found', async () => { |
|
|
|
|
|
const getById = vi.spyOn(DummyDataSource.prototype, 'getById'); |
|
|
|
|
|
getById.mockResolvedValueOnce(null as never); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_NOT_FOUND); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'GET', |
|
|
|
|
|
path: `${BASE_PATH}/pianos/2`, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on item not found on HEAD method', async () => { |
|
|
|
|
|
const getById = vi.spyOn(DummyDataSource.prototype, 'getById'); |
|
|
|
|
|
getById.mockResolvedValueOnce(null as never); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_NOT_FOUND); |
|
|
|
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'HEAD', |
|
|
|
|
|
path: `${BASE_PATH}/pianos/2`, |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on item not found on HEAD method', async () => { |
|
|
|
|
|
const getById = vi.spyOn(DummyDataSource.prototype, 'getById'); |
|
|
|
|
|
getById.mockResolvedValueOnce(null as never); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_NOT_FOUND); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'HEAD', |
|
|
|
|
|
path: `${BASE_PATH}/pianos/2`, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
describe('creating items', () => { |
|
|
|
|
|
const newData = { |
|
|
|
|
|
brand: 'K. Kawai' |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_NOT_FOUND); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const existingResource = { |
|
|
|
|
|
...newData, |
|
|
|
|
|
id: 1, |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
beforeEach(() => { |
|
|
|
|
|
Piano.canCreate(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
describe('creating items', () => { |
|
|
|
|
|
const data = { |
|
|
|
|
|
id: 1, |
|
|
|
|
|
brand: 'Yamaha' |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
afterEach(() => { |
|
|
|
|
|
Piano.canCreate(false); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const newData = { |
|
|
|
|
|
brand: 'K. Kawai' |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
it('throws on error assigning ID', async () => { |
|
|
|
|
|
vi |
|
|
|
|
|
.spyOn(DummyDataSource.prototype, 'newId') |
|
|
|
|
|
.mockImplementationOnce(() => { throw new DummyError() }); |
|
|
|
|
|
|
|
|
beforeEach(async () => { |
|
|
|
|
|
const resourcePath = join(baseDir, 'pianos.jsonl'); |
|
|
|
|
|
await writeFile(resourcePath, JSON.stringify(data)); |
|
|
|
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'POST', |
|
|
|
|
|
path: `${BASE_PATH}/pianos`, |
|
|
|
|
|
body: newData, |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
beforeEach(() => { |
|
|
|
|
|
Piano.canCreate(); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Unable To Assign ID From Piano Data Source'); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
afterEach(() => { |
|
|
|
|
|
Piano.canCreate(false); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on error creating resource', async () => { |
|
|
|
|
|
vi |
|
|
|
|
|
.spyOn(DummyDataSource.prototype, 'newId') |
|
|
|
|
|
.mockResolvedValueOnce(existingResource.id as never); |
|
|
|
|
|
|
|
|
it('throws on error assigning ID', async () => { |
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'POST', |
|
|
|
|
|
path: `${BASE_PATH}/pianos`, |
|
|
|
|
|
body: newData, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
vi |
|
|
|
|
|
.spyOn(DummyDataSource.prototype, 'create') |
|
|
|
|
|
.mockImplementationOnce(() => { throw new DummyError() }); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Unable To Assign ID From Piano Data Source'); |
|
|
|
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'POST', |
|
|
|
|
|
path: `${BASE_PATH}/pianos`, |
|
|
|
|
|
body: newData, |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on error creating resource', async () => { |
|
|
|
|
|
const getById = vi.spyOn(DummyDataSource.prototype, 'newId'); |
|
|
|
|
|
getById.mockResolvedValueOnce(data.id as never); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Unable To Create Piano'); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
describe('patching items', () => { |
|
|
|
|
|
const existingResource = { |
|
|
|
|
|
id: 1, |
|
|
|
|
|
brand: 'Yamaha' |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'POST', |
|
|
|
|
|
path: `${BASE_PATH}/pianos`, |
|
|
|
|
|
body: newData, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const newData = { |
|
|
|
|
|
brand: 'K. Kawai' |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Unable To Create Piano'); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
beforeEach(() => { |
|
|
|
|
|
Piano.canPatch(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
describe.skip('patching items', () => { |
|
|
|
|
|
const data = { |
|
|
|
|
|
id: 1, |
|
|
|
|
|
brand: 'Yamaha' |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
afterEach(() => { |
|
|
|
|
|
Piano.canPatch(false); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const newData = { |
|
|
|
|
|
brand: 'K. Kawai' |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
// TODO add more tests |
|
|
|
|
|
|
|
|
beforeEach(async () => { |
|
|
|
|
|
const resourcePath = join(baseDir, 'pianos.jsonl'); |
|
|
|
|
|
await writeFile(resourcePath, JSON.stringify(data)); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on unable to fetch existing item', async () => { |
|
|
|
|
|
vi |
|
|
|
|
|
.spyOn(DummyDataSource.prototype, 'getById') |
|
|
|
|
|
.mockImplementationOnce(() => { throw new DummyError() }); |
|
|
|
|
|
|
|
|
beforeEach(() => { |
|
|
|
|
|
Piano.canPatch(); |
|
|
|
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'PATCH', |
|
|
|
|
|
path: `${BASE_PATH}/pianos/${existingResource.id}`, |
|
|
|
|
|
body: newData, |
|
|
|
|
|
headers: { |
|
|
|
|
|
'content-type': 'application/merge-patch+json', |
|
|
|
|
|
}, |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
afterEach(() => { |
|
|
|
|
|
Piano.canPatch(false); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Unable To Fetch Piano'); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on item to patch not found', () => { |
|
|
|
|
|
return new Promise<void>((resolve, reject) => { |
|
|
|
|
|
const req = request( |
|
|
|
|
|
{ |
|
|
|
|
|
host: HOST, |
|
|
|
|
|
port: PORT, |
|
|
|
|
|
path: `${BASE_PATH}/pianos/2`, |
|
|
|
|
|
method: 'PATCH', |
|
|
|
|
|
headers: { |
|
|
|
|
|
'Accept': ACCEPT, |
|
|
|
|
|
'Accept-Language': ACCEPT_LANGUAGE, |
|
|
|
|
|
'Content-Type': `${CONTENT_TYPE}; charset="${CONTENT_TYPE_CHARSET}"`, |
|
|
|
|
|
}, |
|
|
|
|
|
}, |
|
|
|
|
|
(res) => { |
|
|
|
|
|
res.on('error', (err) => { |
|
|
|
|
|
reject(err); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_NOT_FOUND); |
|
|
|
|
|
resolve(); |
|
|
|
|
|
}, |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
req.on('error', (err) => { |
|
|
|
|
|
reject(err); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
req.write(JSON.stringify(newData)); |
|
|
|
|
|
req.end(); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on item to patch not found', async () => { |
|
|
|
|
|
vi |
|
|
|
|
|
.spyOn(DummyDataSource.prototype, 'getById') |
|
|
|
|
|
.mockResolvedValueOnce(null as never); |
|
|
|
|
|
|
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'PATCH', |
|
|
|
|
|
path: `${BASE_PATH}/pianos/${existingResource.id}`, |
|
|
|
|
|
body: newData, |
|
|
|
|
|
headers: { |
|
|
|
|
|
'content-type': 'application/merge-patch+json', |
|
|
|
|
|
}, |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
describe.skip('emplacing items', () => { |
|
|
|
|
|
const data = { |
|
|
|
|
|
id: 1, |
|
|
|
|
|
brand: 'Yamaha' |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_NOT_FOUND); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Patch Non-Existing Piano'); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const newData = { |
|
|
|
|
|
id: 1, |
|
|
|
|
|
brand: 'K. Kawai' |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
describe.skip('emplacing items', () => { |
|
|
|
|
|
const existingResource = { |
|
|
|
|
|
id: 1, |
|
|
|
|
|
brand: 'Yamaha' |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
beforeEach(async () => { |
|
|
|
|
|
const resourcePath = join(baseDir, 'pianos.jsonl'); |
|
|
|
|
|
await writeFile(resourcePath, JSON.stringify(data)); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
beforeEach(() => { |
|
|
|
|
|
vi |
|
|
|
|
|
.spyOn(DummyDataSource.prototype, 'getById') |
|
|
|
|
|
.mockResolvedValueOnce(existingResource as never); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
beforeEach(() => { |
|
|
|
|
|
Piano.canEmplace(); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const newData = { |
|
|
|
|
|
id: 1, |
|
|
|
|
|
brand: 'K. Kawai' |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
afterEach(() => { |
|
|
|
|
|
Piano.canEmplace(false); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
beforeEach(() => { |
|
|
|
|
|
Piano.canEmplace(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
describe('deleting items', () => { |
|
|
|
|
|
const data = { |
|
|
|
|
|
id: 1, |
|
|
|
|
|
brand: 'Yamaha' |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
afterEach(() => { |
|
|
|
|
|
Piano.canEmplace(false); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
beforeEach(async () => { |
|
|
|
|
|
const resourcePath = join(baseDir, 'pianos.jsonl'); |
|
|
|
|
|
await writeFile(resourcePath, JSON.stringify(data)); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
describe('deleting items', () => { |
|
|
|
|
|
const existingResource = { |
|
|
|
|
|
id: 1, |
|
|
|
|
|
brand: 'Yamaha' |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
beforeEach(() => { |
|
|
|
|
|
Piano.canDelete(); |
|
|
|
|
|
backend.throwsErrorOnDeletingNotFound(); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
beforeEach(() => { |
|
|
|
|
|
Piano.canDelete(); |
|
|
|
|
|
backend.throwsErrorOnDeletingNotFound(); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
afterEach(() => { |
|
|
|
|
|
Piano.canDelete(false); |
|
|
|
|
|
backend.throwsErrorOnDeletingNotFound(false); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
afterEach(() => { |
|
|
|
|
|
Piano.canDelete(false); |
|
|
|
|
|
backend.throwsErrorOnDeletingNotFound(false); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on unable to check if item exists', async () => { |
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'DELETE', |
|
|
|
|
|
path: `${BASE_PATH}/pianos/2`, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on unable to check if item exists', async () => { |
|
|
|
|
|
vi |
|
|
|
|
|
.spyOn(DummyDataSource.prototype, 'getById') |
|
|
|
|
|
.mockImplementationOnce(() => { throw new DummyError() }); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Unable To Fetch Piano'); |
|
|
|
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'DELETE', |
|
|
|
|
|
path: `${BASE_PATH}/pianos/2`, |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on item not found', async () => { |
|
|
|
|
|
const getById = vi.spyOn(DummyDataSource.prototype, 'getById'); |
|
|
|
|
|
getById.mockResolvedValueOnce(null as never); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Unable To Fetch Piano'); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'DELETE', |
|
|
|
|
|
path: `${BASE_PATH}/pianos/2`, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on item not found', async () => { |
|
|
|
|
|
vi |
|
|
|
|
|
.spyOn(DummyDataSource.prototype, 'getById') |
|
|
|
|
|
.mockResolvedValueOnce(null as never); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_NOT_FOUND); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Delete Non-Existing Piano'); |
|
|
|
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'DELETE', |
|
|
|
|
|
path: `${BASE_PATH}/pianos/${existingResource.id}`, |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on unable to delete item', async () => { |
|
|
|
|
|
const getById = vi.spyOn(DummyDataSource.prototype, 'getById'); |
|
|
|
|
|
getById.mockResolvedValueOnce({ |
|
|
|
|
|
id: 2 |
|
|
|
|
|
} as never); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_NOT_FOUND); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Delete Non-Existing Piano'); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'DELETE', |
|
|
|
|
|
path: `${BASE_PATH}/pianos/2`, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
it('throws on unable to delete item', async () => { |
|
|
|
|
|
vi |
|
|
|
|
|
.spyOn(DummyDataSource.prototype, 'getById') |
|
|
|
|
|
.mockResolvedValueOnce(existingResource as never); |
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Unable To Delete Piano'); |
|
|
|
|
|
|
|
|
vi |
|
|
|
|
|
.spyOn(DummyDataSource.prototype, 'delete') |
|
|
|
|
|
.mockImplementationOnce(() => { throw new DummyError() }); |
|
|
|
|
|
|
|
|
|
|
|
const [res] = await client({ |
|
|
|
|
|
method: 'DELETE', |
|
|
|
|
|
path: `${BASE_PATH}/pianos/${existingResource.id}`, |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
expect(res).toHaveProperty('statusCode', constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); |
|
|
|
|
|
expect(res).toHaveProperty('statusMessage', 'Unable To Delete Piano'); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |