From c2d061c093c84324a6dc9de44df97370e559206d Mon Sep 17 00:00:00 2001 From: Darryl Morton Date: Tue, 10 May 2022 08:48:50 +0100 Subject: [PATCH 1/9] post project route accepts client id or client creation, one of schemas, tests --- app/api-v1/api-doc.js | 55 +++- app/api-v1/routes/profiler/project.js | 5 +- app/api-v1/routes/profiler/project/{id}.js | 2 +- app/api-v1/services/apiService.js | 14 +- {test/helper/seeds => seeds}/project.js | 2 +- test/helper/appHelper.js | 42 ++- test/integration/clientRoutes.test.js | 2 +- test/integration/projectRoutes.test.js | 309 +++++++++++++-------- 8 files changed, 302 insertions(+), 129 deletions(-) rename {test/helper/seeds => seeds}/project.js (92%) diff --git a/app/api-v1/api-doc.js b/app/api-v1/api-doc.js index efea814..f0d6e01 100644 --- a/app/api-v1/api-doc.js +++ b/app/api-v1/api-doc.js @@ -128,7 +128,7 @@ const apiDoc = { }, required: ['id', 'clientId', 'name', 'description'], }, - PostAndPutProject: { + PostAndPutProjectWithClientId: { description: 'Schema for creating/updating a project', type: 'object', properties: { @@ -170,6 +170,59 @@ const apiDoc = { }, required: ['clientId', 'name', 'description'], }, + PostProjectWithClient: { + description: 'Schema for creating/updating a project', + type: 'object', + properties: { + firstName: { + description: 'First name of the client', + type: 'string', + }, + lastName: { + description: 'Last name of the client', + type: 'string', + }, + company: { + description: 'Company of the client', + type: 'string', + }, + role: { + description: 'Role of the client', + type: 'string', + }, + name: { + description: 'Name of the project', + type: 'string', + }, + description: { + description: 'Description of the project', + type: 'string', + }, + startDate: { + description: 'Start date of the project', + type: 'string', + format: 'date-time', + nullable: true, + }, + endDate: { + description: 'End date of the project', + type: 'string', + format: 'date-time', + nullable: true, + }, + budget: { + description: 'Budget of the project', + type: 'number', + nullable: true, + }, + documentUrl: { + description: 'Document url of the project', + type: 'string', + nullable: true, + }, + }, + required: ['firstName', 'lastName', 'company', 'role', 'name', 'description'], + }, }, }, paths: {}, diff --git a/app/api-v1/routes/profiler/project.js b/app/api-v1/routes/profiler/project.js index a4db940..f59f1e7 100644 --- a/app/api-v1/routes/profiler/project.js +++ b/app/api-v1/routes/profiler/project.js @@ -49,7 +49,10 @@ module.exports = function (apiService) { content: { 'application/json': { schema: { - $ref: '#/components/schemas/PostAndPutProject', + oneOf: [ + { $ref: '#/components/schemas/PostAndPutProjectWithClientId' }, + { $ref: '#/components/schemas/PostProjectWithClient' }, + ], }, }, }, diff --git a/app/api-v1/routes/profiler/project/{id}.js b/app/api-v1/routes/profiler/project/{id}.js index 5976570..7c03e71 100644 --- a/app/api-v1/routes/profiler/project/{id}.js +++ b/app/api-v1/routes/profiler/project/{id}.js @@ -93,7 +93,7 @@ module.exports = function (apiService) { content: { 'application/json': { schema: { - $ref: '#/components/schemas/PostAndPutProject', + $ref: '#/components/schemas/PostAndPutProjectWithClientId', }, }, }, diff --git a/app/api-v1/services/apiService.js b/app/api-v1/services/apiService.js index 738f3de..f36f52c 100644 --- a/app/api-v1/services/apiService.js +++ b/app/api-v1/services/apiService.js @@ -78,9 +78,19 @@ async function postProject(reqBody) { const findResult = await findProjectByNameDb(reqBody.name) if (findResult.length === 0) { - const result = await addProjectDb(reqBody) + if (reqBody.firstName || reqBody.lastName || reqBody.company || reqBody.role) { + const { statusCode: clientStatusCode, result: clientResult } = await postClient(reqBody) - return { statusCode: 201, result: result[0] } + if (clientStatusCode === 201) { + const result = await addProjectDb({ ...reqBody, clientId: clientResult.id }) + + return { statusCode: 201, result: result[0] } + } + } else { + const result = await addProjectDb(reqBody) + + return { statusCode: 201, result: result[0] } + } } else { return { statusCode: 409, result: {} } } diff --git a/test/helper/seeds/project.js b/seeds/project.js similarity index 92% rename from test/helper/seeds/project.js rename to seeds/project.js index 5e8a168..45ce6f4 100644 --- a/test/helper/seeds/project.js +++ b/seeds/project.js @@ -1,4 +1,4 @@ -const { client } = require('../../../app/db') +const { client } = require('../app/db') const cleanup = async (tableName) => { await client(tableName).del() diff --git a/test/helper/appHelper.js b/test/helper/appHelper.js index 2225ff3..4b566c1 100644 --- a/test/helper/appHelper.js +++ b/test/helper/appHelper.js @@ -31,6 +31,16 @@ const createDefaultClient = () => { }) } +const createDefaultProjectWithClient = (project, client) => { + const projectObj = createProject(project) + const clientObj = createDefaultClient(client) + + return { + ...projectObj, + ...clientObj, + } +} + const assertUuidV4 = (id) => { expect(uuidValidate(id) && uuidVersion(id) === 4).to.be.true } @@ -53,20 +63,43 @@ const assertGetClients = (actualResults, expectedResults) => { const assertPostProjectRequiredParams = (actualResult, expectedResult) => { assertUuidV4(actualResult.id) + expect(actualResult.clientId).to.equal(expectedResult.clientId) expect(actualResult.name).to.equal(expectedResult.name) expect(actualResult.description).to.equal(expectedResult.description) } -const assertPostProjectParams = (actualResult, expectedResult) => { - assertPostProjectRequiredParams(actualResult, expectedResult) - +const assertPostProjectOptionalParams = (actualResult, expectedResult) => { expect(actualResult.startDate).to.equal(expectedResult.startDate) expect(actualResult.endDate).to.equal(expectedResult.endDate) expect(actualResult.budget).to.equal(expectedResult.budget) expect(actualResult.documentUrl).to.equal(expectedResult.documentUrl) } +const assertPostProjectParams = (actualResult, expectedResult) => { + assertPostProjectRequiredParams(actualResult, expectedResult) + + assertPostProjectOptionalParams(actualResult, expectedResult) +} + +const assertPostProjectWithUnknownClientIdRequiredParams = (actualResult, expectedResult) => { + assertUuidV4(actualResult.id) + assertUuidV4(actualResult.clientId) + + expect(actualResult.name).to.equal(expectedResult.name) + expect(actualResult.description).to.equal(expectedResult.description) +} + +const assertPostProjectWithUnknownClientIdParams = (actualResult, expectedResult) => { + assertUuidV4(actualResult.id) + assertUuidV4(actualResult.clientId) + + expect(actualResult.name).to.equal(expectedResult.name) + expect(actualResult.description).to.equal(expectedResult.description) + + assertPostProjectOptionalParams(actualResult, expectedResult) +} + const assertGetProjects = (actualResults, expectedResults) => { expect(actualResults.length).to.equal(expectedResults.length) @@ -82,8 +115,11 @@ module.exports = { assertGetClients, createProject, createDefaultProject, + createDefaultProjectWithClient, assertUuidV4, assertPostProjectParams, assertPostProjectRequiredParams, + assertPostProjectWithUnknownClientIdRequiredParams, + assertPostProjectWithUnknownClientIdParams, assertGetProjects, } diff --git a/test/integration/clientRoutes.test.js b/test/integration/clientRoutes.test.js index 3ce40bc..22b2ea2 100644 --- a/test/integration/clientRoutes.test.js +++ b/test/integration/clientRoutes.test.js @@ -10,7 +10,7 @@ const { putClientRoute, deleteClientRoute, } = require('../helper/clientRouteHelper') -const { cleanupAll, cleanup } = require('../helper/seeds/project') +const { cleanupAll, cleanup } = require('../../seeds/project') describe('Client routes', function () { let app diff --git a/test/integration/projectRoutes.test.js b/test/integration/projectRoutes.test.js index 5af29b6..bb57e6b 100644 --- a/test/integration/projectRoutes.test.js +++ b/test/integration/projectRoutes.test.js @@ -8,6 +8,9 @@ const { assertPostProjectParams, assertPostProjectRequiredParams, assertGetProjects, + createDefaultProjectWithClient, + createDefaultClient, + assertPostProjectWithUnknownClientIdParams, } = require('../helper/appHelper') const { createHttpServer } = require('../../app/server') const { @@ -17,181 +20,249 @@ const { putProjectRoute, deleteProjectRoute, } = require('../helper/projectRouteHelper') -const { seed, cleanup } = require('../helper/seeds/project') +const { seed, cleanup, cleanupAll } = require('../../seeds/project') describe('Project routes', function () { let app let clientId let invalidId + let defaultProjectWithClientId let defaultProject + let defaultClient + let defaultProjectWithClient before(async function () { - await seed() - app = await createHttpServer() clientId = 'c7b9e848-e2bb-456d-8eaa-129c1cb3580c' invalidId = '00000000-0000-0000-0000-000000000000' - defaultProject = createDefaultProject({ clientId }) + defaultProjectWithClientId = createDefaultProject({ clientId }) + defaultClient = createDefaultClient() + defaultProject = createDefaultProject({}) + defaultProjectWithClient = createDefaultProjectWithClient(defaultProject, defaultClient) }) - beforeEach(async function () { - await cleanup('projects') - }) + describe('Project with client id', function () { + before(async function () { + await seed() + }) - test('POST Project all request body parameters', async function () { - const expectedResult = defaultProject + beforeEach(async function () { + await cleanup('projects') + }) - const actualResponse = await postProjectRoute(defaultProject, app) + test('POST Project with client id - all request body parameters', async function () { + const expectedResult = defaultProjectWithClientId - expect(actualResponse.status).to.equal(201) - assertPostProjectParams(actualResponse.body, expectedResult) - }) + const actualResponse = await postProjectRoute(defaultProjectWithClientId, app) - test('POST Project with only required request body parameters', async function () { - const project = createProject({ clientId, name: 'Project 1', description: 'Project 1 description' }) - const expectedResult = { - ...project, - startDate: null, - endDate: null, - budget: null, - documentUrl: null, - } + expect(actualResponse.status).to.equal(201) + assertPostProjectParams(actualResponse.body, expectedResult) + }) - const actualResponse = await postProjectRoute(project, app) + test('POST Project with client id - only required request body parameters', async function () { + const project = createProject({ clientId, name: 'Project 1', description: 'Project 1 description' }) - expect(actualResponse.status).to.equal(201) - assertPostProjectRequiredParams(actualResponse.body, expectedResult) - }) + const expectedResult = { + ...project, + startDate: null, + endDate: null, + budget: null, + documentUrl: null, + } - test('POST Project missing client id', async function () { - const invalidProject = createProject({ name: 'Project 1', description: 'Project 1 description' }) + const actualResponse = await postProjectRoute(project, app) - const actualResponse = await postProjectRoute(invalidProject, app) + expect(actualResponse.status).to.equal(201) + assertPostProjectRequiredParams(actualResponse.body, expectedResult) + }) - expect(actualResponse.status).to.equal(400) - expect(actualResponse.body).deep.equal({}) - }) + test('POST Project with client id - missing client id', async function () { + const invalidProject = createProject({ name: 'Project 1', description: 'Project 1 description' }) - test('POST invalid project', async function () { - const actualResponse = await postProjectRoute({}, app) + const actualResponse = await postProjectRoute(invalidProject, app) - expect(actualResponse.status).to.equal(400) - expect(actualResponse.body).deep.equal({}) - }) + expect(actualResponse.status).to.equal(400) + expect(actualResponse.body).deep.equal({}) + }) - test('POST duplicate project', async function () { - await postProjectRoute(defaultProject, app) - const actualResponse = await postProjectRoute(defaultProject, app) + test('POST Project with client id - invalid project', async function () { + const actualResponse = await postProjectRoute({}, app) - expect(actualResponse.status).to.equal(409) - expect(actualResponse.body).deep.equal({}) - }) + expect(actualResponse.status).to.equal(400) + expect(actualResponse.body).deep.equal({}) + }) - test('GET projects', async function () { - const expectedResult = [defaultProject] + test('POST with client id - duplicate project', async function () { + await postProjectRoute(defaultProjectWithClientId, app) + const actualResponse = await postProjectRoute(defaultProjectWithClientId, app) - await postProjectRoute(defaultProject, app) - const actualResponse = await getProjectsRoute(app) + expect(actualResponse.status).to.equal(409) + expect(actualResponse.body).deep.equal({}) + }) - expect(actualResponse.status).to.equal(200) - assertGetProjects(actualResponse.body, expectedResult) - }) + test('GET projects', async function () { + const expectedResult = [defaultProjectWithClientId] - test('GET project by id', async function () { - const expectedResult = defaultProject + await postProjectRoute(defaultProjectWithClientId, app) + const actualResponse = await getProjectsRoute(app) - const response = await postProjectRoute(defaultProject, app) - const actualResponse = await getProjectByIdRoute(response.body.id, app) + expect(actualResponse.status).to.equal(200) + assertGetProjects(actualResponse.body, expectedResult) + }) - expect(actualResponse.status).to.equal(200) - assertPostProjectRequiredParams(actualResponse.body, expectedResult) - }) + test('GET project by id', async function () { + const expectedResult = defaultProjectWithClientId - test('GET project by id with invalid path id parameter', async function () { - const actualResponse = await getProjectByIdRoute('123', app) + const response = await postProjectRoute(defaultProjectWithClientId, app) + const actualResponse = await getProjectByIdRoute(response.body.id, app) - expect(actualResponse.status).to.equal(400) - }) + expect(actualResponse.status).to.equal(200) + assertPostProjectRequiredParams(actualResponse.body, expectedResult) + }) - test('GET project by id with non-existing project', async function () { - const actualResponse = await getProjectByIdRoute(invalidId, app) + test('GET project by id with invalid path id parameter', async function () { + const actualResponse = await getProjectByIdRoute('123', app) - expect(actualResponse.status).to.equal(404) - }) + expect(actualResponse.status).to.equal(400) + }) - test('PUT project with all request body parameters', async function () { - const updatedProject = createProject({ - clientId, - name: 'Project 2', - description: 'Project 2 description', - startDate: moment().startOf('day').toISOString(), - endDate: moment().endOf('day').toISOString(), - budget: 200000.0, - documentUrl: 'http://digitalcatapult.org.uk/document/url', + test('GET project by id with non-existing project', async function () { + const actualResponse = await getProjectByIdRoute(invalidId, app) + + expect(actualResponse.status).to.equal(404) }) - const expectedResult = updatedProject - const response = await postProjectRoute(defaultProject, app) - const actualResponse = await putProjectRoute(response.body.id, updatedProject, app) + test('PUT project with all request body parameters', async function () { + const updatedProject = createProject({ + clientId, + name: 'Project 2', + description: 'Project 2 description', + startDate: moment().startOf('day').toISOString(), + endDate: moment().endOf('day').toISOString(), + budget: 200000.0, + documentUrl: 'http://digitalcatapult.org.uk/document/url', + }) + const expectedResult = updatedProject + + const response = await postProjectRoute(defaultProjectWithClientId, app) + const actualResponse = await putProjectRoute(response.body.id, updatedProject, app) + + expect(actualResponse.status).to.equal(200) + assertPostProjectParams(actualResponse.body, expectedResult) + }) - expect(actualResponse.status).to.equal(200) - assertPostProjectParams(actualResponse.body, expectedResult) - }) + test('PUT project with only required fields', async function () { + const project = createProject({ clientId, name: 'Project 1', description: 'Project 1 description' }) + const updatedProject = createProject({ clientId, name: 'Project 2', description: 'Project 2 description' }) + const expectedResult = updatedProject - test('PUT project with only required fields', async function () { - const project = createProject({ clientId, name: 'Project 1', description: 'Project 1 description' }) - const updatedProject = createProject({ clientId, name: 'Project 2', description: 'Project 2 description' }) - const expectedResult = updatedProject + const response = await postProjectRoute(project, app) + const actualResponse = await putProjectRoute(response.body.id, updatedProject, app) - const response = await postProjectRoute(project, app) - const actualResponse = await putProjectRoute(response.body.id, updatedProject, app) + expect(actualResponse.status).to.equal(200) + assertPostProjectRequiredParams(actualResponse.body, expectedResult) + }) - expect(actualResponse.status).to.equal(200) - assertPostProjectRequiredParams(actualResponse.body, expectedResult) - }) + test('PUT project with existing name', async function () { + const response = await postProjectRoute(defaultProjectWithClientId, app) + const actualResponse = await putProjectRoute(response.body.id, defaultProjectWithClientId, app) - test('PUT project with existing name', async function () { - const response = await postProjectRoute(defaultProject, app) - const actualResponse = await putProjectRoute(response.body.id, defaultProject, app) + expect(actualResponse.status).to.equal(409) + expect(actualResponse.body).to.deep.equal({}) + }) - expect(actualResponse.status).to.equal(409) - expect(actualResponse.body).to.deep.equal({}) - }) + test('PUT project with non-existing project', async function () { + const actualResponse = await putProjectRoute(invalidId, defaultProjectWithClientId, app) - test('PUT project with non-existing project', async function () { - const actualResponse = await putProjectRoute(invalidId, defaultProject, app) + expect(actualResponse.status).to.equal(404) + }) - expect(actualResponse.status).to.equal(404) - }) + test('PUT project with missing required client id path parameter', async function () { + const invalidProject = createProject({ name: 'Project 2', description: 'Project 2 description' }) - test('PUT project with missing required client id path parameter', async function () { - const invalidProject = createProject({ name: 'Project 2', description: 'Project 2 description' }) + const actualResponse = await putProjectRoute(invalidId, invalidProject, app) - const actualResponse = await putProjectRoute(invalidId, invalidProject, app) + expect(actualResponse.status).to.equal(400) + expect(actualResponse.body).deep.equal({}) + }) - expect(actualResponse.status).to.equal(400) - expect(actualResponse.body).deep.equal({}) - }) + test('DELETE project', async function () { + const response = await postProjectRoute(defaultProjectWithClientId, app) + const actualResponse = await deleteProjectRoute(response.body.id, app) - test('DELETE project', async function () { - const response = await postProjectRoute(defaultProject, app) - const actualResponse = await deleteProjectRoute(response.body.id, app) + expect(actualResponse.status).to.equal(204) + expect(actualResponse.body).deep.equal({}) + }) - expect(actualResponse.status).to.equal(204) - expect(actualResponse.body).deep.equal({}) - }) + test('DELETE project with invalid id path parameter', async function () { + const actualResponse = await deleteProjectRoute('123', app) - test('DELETE project with invalid id path parameter', async function () { - const actualResponse = await deleteProjectRoute('123', app) + expect(actualResponse.status).to.equal(400) + expect(actualResponse.body).deep.equal({}) + }) + + test('DELETE project with non-existing project', async function () { + const actualResponse = await deleteProjectRoute(invalidId, app) - expect(actualResponse.status).to.equal(400) - expect(actualResponse.body).deep.equal({}) + expect(actualResponse.status).to.equal(404) + expect(actualResponse.body).deep.equal({}) + }) }) - test('DELETE project with non-existing project', async function () { - const actualResponse = await deleteProjectRoute(invalidId, app) + describe('Project with Client', function () { + beforeEach(async function () { + await cleanupAll() + }) + + test('POST Project with client - all request body parameters', async function () { + const expectedResult = defaultProject + + const actualResponse = await postProjectRoute(defaultProjectWithClient, app) - expect(actualResponse.status).to.equal(404) - expect(actualResponse.body).deep.equal({}) + expect(actualResponse.status).to.equal(201) + assertPostProjectWithUnknownClientIdParams(actualResponse.body, expectedResult) + }) + + test('POST Project with client - only required request body parameters', async function () { + const project = createProject({ name: 'Project 1', description: 'Project 1 description', ...defaultClient }) + + const actualResponse = await postProjectRoute(project, app) + + const expectedResult = { + clientId: actualResponse.body.clientId, + ...project, + startDate: null, + endDate: null, + budget: null, + documentUrl: null, + } + + expect(actualResponse.status).to.equal(201) + assertPostProjectWithUnknownClientIdParams(actualResponse.body, expectedResult) + }) + + test('POST Project with client - invalid client', async function () { + const invalidProject = createProject({ + name: 'Project 1', + description: 'Project 1 description', + firstName: null, + lastName: null, + company: null, + role: null, + }) + + const actualResponse = await postProjectRoute(invalidProject, app) + + expect(actualResponse.status).to.equal(400) + expect(actualResponse.body).deep.equal({}) + }) + + test('POST with client - duplicate project', async function () { + await postProjectRoute(defaultProjectWithClient, app) + const actualResponse = await postProjectRoute(defaultProjectWithClient, app) + + expect(actualResponse.status).to.equal(409) + expect(actualResponse.body).deep.equal({}) + }) }) }) From 4a757bea9f0a04f0726a9a83875e760f6be70aea Mon Sep 17 00:00:00 2001 From: Darryl Morton Date: Tue, 10 May 2022 08:53:14 +0100 Subject: [PATCH 2/9] version bump --- helm/adcp-profiler-service/Chart.yaml | 4 ++-- helm/adcp-profiler-service/values.yaml | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/helm/adcp-profiler-service/Chart.yaml b/helm/adcp-profiler-service/Chart.yaml index 25cbc1a..03f739f 100644 --- a/helm/adcp-profiler-service/Chart.yaml +++ b/helm/adcp-profiler-service/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 name: adcp-profiler-service -appVersion: '0.0.4' +appVersion: '0.0.5' description: A Helm chart for adcp-profiler-service -version: '0.0.4' +version: '0.0.5' type: application maintainers: - name: digicatapult diff --git a/helm/adcp-profiler-service/values.yaml b/helm/adcp-profiler-service/values.yaml index d0b236d..23cadcd 100644 --- a/helm/adcp-profiler-service/values.yaml +++ b/helm/adcp-profiler-service/values.yaml @@ -3,5 +3,5 @@ config: image: repository: ghcr.io/digicatapult/adcp-profiler-service pullPolicy: IfNotPresent - tag: 'v0.0.4' + tag: 'v0.0.5' pullSecrets: ['ghcr-digicatapult'] diff --git a/package-lock.json b/package-lock.json index e5ac127..2d6dfbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@digicatapult/adcp-profiler-service", - "version": "0.0.4", + "version": "0.0.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@digicatapult/adcp-profiler-service", - "version": "0.0.4", + "version": "0.0.5", "license": "Apache-2.0", "dependencies": { "body-parser": "^1.20.0", diff --git a/package.json b/package.json index 369aaa5..7d91b70 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@digicatapult/adcp-profiler-service", - "version": "0.0.4", + "version": "0.0.5", "description": "Insert repo description", "main": "app/index.js", "scripts": { From edd31249818aee3e775435d64bd90a0bab5a0708 Mon Sep 17 00:00:00 2001 From: Darryl Morton Date: Tue, 10 May 2022 08:57:24 +0100 Subject: [PATCH 3/9] moving seeds directory back to test directory --- test/integration/clientRoutes.test.js | 2 +- test/integration/projectRoutes.test.js | 2 +- {seeds => test/seeds}/project.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename {seeds => test/seeds}/project.js (92%) diff --git a/test/integration/clientRoutes.test.js b/test/integration/clientRoutes.test.js index 22b2ea2..e151917 100644 --- a/test/integration/clientRoutes.test.js +++ b/test/integration/clientRoutes.test.js @@ -10,7 +10,7 @@ const { putClientRoute, deleteClientRoute, } = require('../helper/clientRouteHelper') -const { cleanupAll, cleanup } = require('../../seeds/project') +const { cleanupAll, cleanup } = require('../seeds/project') describe('Client routes', function () { let app diff --git a/test/integration/projectRoutes.test.js b/test/integration/projectRoutes.test.js index bb57e6b..9aede1f 100644 --- a/test/integration/projectRoutes.test.js +++ b/test/integration/projectRoutes.test.js @@ -20,7 +20,7 @@ const { putProjectRoute, deleteProjectRoute, } = require('../helper/projectRouteHelper') -const { seed, cleanup, cleanupAll } = require('../../seeds/project') +const { seed, cleanup, cleanupAll } = require('../seeds/project') describe('Project routes', function () { let app diff --git a/seeds/project.js b/test/seeds/project.js similarity index 92% rename from seeds/project.js rename to test/seeds/project.js index 45ce6f4..4e82c0b 100644 --- a/seeds/project.js +++ b/test/seeds/project.js @@ -1,4 +1,4 @@ -const { client } = require('../app/db') +const { client } = require('../../app/db') const cleanup = async (tableName) => { await client(tableName).del() From b52cf124c96cd15ea258135a525fba5cf406e266 Mon Sep 17 00:00:00 2001 From: Darryl Morton Date: Tue, 10 May 2022 09:35:26 +0100 Subject: [PATCH 4/9] cleanup --- test/helper/appHelper.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/helper/appHelper.js b/test/helper/appHelper.js index 4b566c1..f5ce630 100644 --- a/test/helper/appHelper.js +++ b/test/helper/appHelper.js @@ -47,6 +47,7 @@ const assertUuidV4 = (id) => { const assertClientParams = (actualResult, expectedResult) => { assertUuidV4(actualResult.id) + expect(actualResult.firstName).to.equal(expectedResult.firstName) expect(actualResult.lastName).to.equal(expectedResult.lastName) expect(actualResult.company).to.equal(expectedResult.company) From e244e92e109eb9db20420e51fae0faca5aa33e57 Mon Sep 17 00:00:00 2001 From: Darryl Morton Date: Tue, 10 May 2022 16:18:35 +0100 Subject: [PATCH 5/9] tweaks for post project with client and client id scenarios. added missing client lookup via id. --- app/api-v1/services/apiService.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/app/api-v1/services/apiService.js b/app/api-v1/services/apiService.js index f36f52c..a1beec1 100644 --- a/app/api-v1/services/apiService.js +++ b/app/api-v1/services/apiService.js @@ -10,6 +10,7 @@ const { findClientByIdDb, updateClientDb, removeClientDb, + client, } = require('../../db') async function getClients() { @@ -78,7 +79,17 @@ async function postProject(reqBody) { const findResult = await findProjectByNameDb(reqBody.name) if (findResult.length === 0) { - if (reqBody.firstName || reqBody.lastName || reqBody.company || reqBody.role) { + if (reqBody.clientId) { + const { statusCode: clientStatusCode } = await getClientById(reqBody.clientId) + + if (clientStatusCode === 200) { + const result = await addProjectDb(reqBody) + + return { statusCode: 201, result: result[0] } + } else { + return { statusCode: clientStatusCode, result: {} } + } + } else { const { statusCode: clientStatusCode, result: clientResult } = await postClient(reqBody) if (clientStatusCode === 201) { @@ -86,10 +97,6 @@ async function postProject(reqBody) { return { statusCode: 201, result: result[0] } } - } else { - const result = await addProjectDb(reqBody) - - return { statusCode: 201, result: result[0] } } } else { return { statusCode: 409, result: {} } From 4ca02a3cbafef032ca62d051014d061d7cb976fb Mon Sep 17 00:00:00 2001 From: Darryl Morton Date: Tue, 10 May 2022 16:19:21 +0100 Subject: [PATCH 6/9] cleanup --- app/api-v1/services/apiService.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/api-v1/services/apiService.js b/app/api-v1/services/apiService.js index a1beec1..8cac4bd 100644 --- a/app/api-v1/services/apiService.js +++ b/app/api-v1/services/apiService.js @@ -10,7 +10,6 @@ const { findClientByIdDb, updateClientDb, removeClientDb, - client, } = require('../../db') async function getClients() { From 576b4e0831e9907571219330b552be3c6c0ecbf3 Mon Sep 17 00:00:00 2001 From: Darryl Morton Date: Wed, 11 May 2022 08:34:41 +0100 Subject: [PATCH 7/9] port number change --- app/env.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/env.js b/app/env.js index 0e45cbd..767102f 100644 --- a/app/env.js +++ b/app/env.js @@ -12,7 +12,7 @@ const vars = envalid.cleanEnv( process.env, { SERVICE_TYPE: envalid.str({ default: 'adcp-profiler-service'.toUpperCase().replace(/-/g, '_') }), - PORT: envalid.port({ default: 80, devDefault: 3000 }), + PORT: envalid.port({ default: 80, devDefault: 3001 }), API_VERSION: envalid.str({ default: version }), API_MAJOR_VERSION: envalid.str({ default: 'v1' }), LOG_LEVEL: envalid.str({ default: 'info', devDefault: 'debug' }), From a3b68e439df30b05bc36b3d924cc8374246cf7ba Mon Sep 17 00:00:00 2001 From: Darryl Morton Date: Tue, 17 May 2022 16:10:27 +0100 Subject: [PATCH 8/9] changes for post/put schema - 409 PUT if name exists on another project not same project, as name can be unchanged upon update --- app/api-v1/api-doc.js | 2 +- app/api-v1/routes/profiler/project.js | 2 +- app/api-v1/routes/profiler/project/{id}.js | 5 ++- app/api-v1/services/apiService.js | 22 ++++++++----- .../project/putProjectResponseValidator.js | 1 + app/db.js | 5 +++ test/integration/projectRoutes.test.js | 31 +++++++++++++++++-- 7 files changed, 55 insertions(+), 13 deletions(-) diff --git a/app/api-v1/api-doc.js b/app/api-v1/api-doc.js index f0d6e01..4696772 100644 --- a/app/api-v1/api-doc.js +++ b/app/api-v1/api-doc.js @@ -170,7 +170,7 @@ const apiDoc = { }, required: ['clientId', 'name', 'description'], }, - PostProjectWithClient: { + PostAndPutProjectWithClient: { description: 'Schema for creating/updating a project', type: 'object', properties: { diff --git a/app/api-v1/routes/profiler/project.js b/app/api-v1/routes/profiler/project.js index f59f1e7..460ec52 100644 --- a/app/api-v1/routes/profiler/project.js +++ b/app/api-v1/routes/profiler/project.js @@ -51,7 +51,7 @@ module.exports = function (apiService) { schema: { oneOf: [ { $ref: '#/components/schemas/PostAndPutProjectWithClientId' }, - { $ref: '#/components/schemas/PostProjectWithClient' }, + { $ref: '#/components/schemas/PostAndPutProjectWithClient' }, ], }, }, diff --git a/app/api-v1/routes/profiler/project/{id}.js b/app/api-v1/routes/profiler/project/{id}.js index 7c03e71..105b51d 100644 --- a/app/api-v1/routes/profiler/project/{id}.js +++ b/app/api-v1/routes/profiler/project/{id}.js @@ -93,7 +93,10 @@ module.exports = function (apiService) { content: { 'application/json': { schema: { - $ref: '#/components/schemas/PostAndPutProjectWithClientId', + oneOf: [ + { $ref: '#/components/schemas/PostAndPutProjectWithClientId' }, + { $ref: '#/components/schemas/PostAndPutProjectWithClient' }, + ], }, }, }, diff --git a/app/api-v1/services/apiService.js b/app/api-v1/services/apiService.js index 8cac4bd..c11d023 100644 --- a/app/api-v1/services/apiService.js +++ b/app/api-v1/services/apiService.js @@ -10,6 +10,7 @@ const { findClientByIdDb, updateClientDb, removeClientDb, + findProjectByNameAndWhereNotIdDb, } = require('../../db') async function getClients() { @@ -103,16 +104,23 @@ async function postProject(reqBody) { } async function putProject(id, reqBody) { - const findResult = await findProjectByIdDb(id) + // lookup existing project by id + const findProjectByIdResult = await findProjectByIdDb(id) - if (findResult.length === 0) { + // exists? + if (findProjectByIdResult.length === 0) { return { statusCode: 404, result: {} } - } else if (findResult[0].name !== reqBody.name) { - const result = await updateProjectDb(id, reqBody) - - return { statusCode: 200, result: result[0] } } else { - return { statusCode: 409, result: {} } + // lookup all projects by name in case of clash, but exclude current project id as not a clash if name is unchanged + const findProjectByNameAndWhereNotIdResult = await findProjectByNameAndWhereNotIdDb(reqBody.name, id) + + if (findProjectByNameAndWhereNotIdResult.length === 0) { + const result = await updateProjectDb(id, reqBody) + + return { statusCode: 200, result: result[0] } + } else { + return { statusCode: 409, result: {} } + } } } diff --git a/app/api-v1/validators/project/putProjectResponseValidator.js b/app/api-v1/validators/project/putProjectResponseValidator.js index 445908c..5608eea 100644 --- a/app/api-v1/validators/project/putProjectResponseValidator.js +++ b/app/api-v1/validators/project/putProjectResponseValidator.js @@ -14,6 +14,7 @@ const PUT_PROJECT_RESPONSES = { }, 400: apiDocResponses['400'], 404: apiDocResponses['404'], + 409: apiDocResponses['409'], default: apiDocResponses.default, } diff --git a/app/db.js b/app/db.js index ff7858a..6326f52 100644 --- a/app/db.js +++ b/app/db.js @@ -94,6 +94,10 @@ async function findProjectByNameDb(name) { return client('projects').select('name').where({ name }) } +async function findProjectByNameAndWhereNotIdDb(name, id) { + return client('projects').select('name').where({ name }).andWhereNot({ id }) +} + async function findProjectByIdDb(id) { return client('projects') .select([ @@ -140,6 +144,7 @@ module.exports = { removeClientDb, findProjectsDb, findProjectByNameDb, + findProjectByNameAndWhereNotIdDb, addProjectDb, findProjectByIdDb, removeProjectDb, diff --git a/test/integration/projectRoutes.test.js b/test/integration/projectRoutes.test.js index 9aede1f..9f272e9 100644 --- a/test/integration/projectRoutes.test.js +++ b/test/integration/projectRoutes.test.js @@ -163,9 +163,34 @@ describe('Project routes', function () { assertPostProjectRequiredParams(actualResponse.body, expectedResult) }) - test('PUT project with existing name', async function () { - const response = await postProjectRoute(defaultProjectWithClientId, app) - const actualResponse = await putProjectRoute(response.body.id, defaultProjectWithClientId, app) + test('PUT project with existing name of same project', async function () { + const project = createProject({ clientId, name: 'Project 1', description: 'Project 1 description' }) + const updatedProject = createProject({ + clientId, + name: 'Project 1', + description: 'Project 1 description updated', + }) + const expectedResult = updatedProject + + const response = await postProjectRoute(project, app) + const actualResponse = await putProjectRoute(response.body.id, updatedProject, app) + + expect(actualResponse.status).to.equal(200) + assertPostProjectRequiredParams(actualResponse.body, expectedResult) + }) + + test('PUT project with existing name of different project', async function () { + const projectOne = createProject({ clientId, name: 'Project 1', description: 'Project 1 description' }) + const projectTwo = createProject({ clientId, name: 'Project 2', description: 'Project 2 description' }) + const updatedProjectTwo = createProject({ + clientId, + name: 'Project 1', + description: 'Project 2 description updated', + }) + + await postProjectRoute(projectOne, app) + const response = await postProjectRoute(projectTwo, app) + const actualResponse = await putProjectRoute(response.body.id, updatedProjectTwo, app) expect(actualResponse.status).to.equal(409) expect(actualResponse.body).to.deep.equal({}) From efe9d2d6846f4fa7d3dedac5f6603267fedefbad Mon Sep 17 00:00:00 2001 From: Darryl Morton Date: Wed, 18 May 2022 15:22:17 +0100 Subject: [PATCH 9/9] put project logic missing check for client id same as post --- app/api-v1/services/apiService.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/api-v1/services/apiService.js b/app/api-v1/services/apiService.js index c11d023..8592f73 100644 --- a/app/api-v1/services/apiService.js +++ b/app/api-v1/services/apiService.js @@ -115,9 +115,19 @@ async function putProject(id, reqBody) { const findProjectByNameAndWhereNotIdResult = await findProjectByNameAndWhereNotIdDb(reqBody.name, id) if (findProjectByNameAndWhereNotIdResult.length === 0) { - const result = await updateProjectDb(id, reqBody) + if (reqBody.clientId) { + const result = await updateProjectDb(id, reqBody) - return { statusCode: 200, result: result[0] } + return { statusCode: 200, result: result[0] } + } else { + const { statusCode: clientStatusCode, result: clientResult } = await postClient(reqBody) + + if (clientStatusCode === 201) { + const result = await updateProjectDb(id, { ...reqBody, clientId: clientResult.id }) + + return { statusCode: 200, result: result[0] } + } + } } else { return { statusCode: 409, result: {} } }