From 05d06d50e518e8fa8956a7cc71950204214ffd86 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Wed, 27 Apr 2022 16:15:15 -0300 Subject: [PATCH 01/12] Add PATCH to update the key of the metadata track --- .../controllers/metadataTrackController.js | 26 ++++++++-- src/api.v2/routes/metadataTrack.js | 5 ++ src/specs/api.v2.yaml | 49 +++++++++++++++++++ src/sql/migrations/20220304184711_schema.js | 2 + 4 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/api.v2/controllers/metadataTrackController.js b/src/api.v2/controllers/metadataTrackController.js index aa9d098d7..0b08c7aac 100644 --- a/src/api.v2/controllers/metadataTrackController.js +++ b/src/api.v2/controllers/metadataTrackController.js @@ -1,7 +1,7 @@ const MetadataTrack = require('../model/MetadataTrack'); const getLogger = require('../../utils/getLogger'); -const { OK } = require('../../utils/responses'); +const { OK, NotFoundError } = require('../../utils/responses'); const logger = getLogger('[MetadataTrackController] - '); @@ -10,15 +10,35 @@ const createMetadataTrack = async (req, res) => { params: { experimentId, metadataTrackKey }, } = req; - logger.log(`Creating metadata track ${metadataTrackKey}`); + logger.log(`Creating metadata track ${metadataTrackKey} in experiment ${experimentId}`); await new MetadataTrack().createNewMetadataTrack(experimentId, metadataTrackKey); - logger.log(`Finished creating metadata track ${metadataTrackKey} for experiment ${experimentId}`); + logger.log(`Finished creating metadata track ${metadataTrackKey} in experiment ${experimentId}`); res.json(OK()); }; +const patchMetadataTrack = async (req, res) => { + const { + params: { experimentId, metadataTrackKey: oldKey }, + body: { key }, + } = req; + + logger.log(`Patching metadata track ${oldKey} in experiment ${experimentId}`); + + const result = await new MetadataTrack() + .update({ experiment_id: experimentId, key: oldKey }, { key }); + + if (result.length === 0) { + throw new NotFoundError(`Metadata track ${oldKey} doesn't exist`); + } + + logger.log(`Finished patching metadata track ${oldKey} in experiment ${experimentId}, changed to ${key}`); + res.json(OK()); +}; + module.exports = { createMetadataTrack, + patchMetadataTrack, }; diff --git a/src/api.v2/routes/metadataTrack.js b/src/api.v2/routes/metadataTrack.js index cee899446..2fc305a2e 100644 --- a/src/api.v2/routes/metadataTrack.js +++ b/src/api.v2/routes/metadataTrack.js @@ -1,5 +1,6 @@ const { createMetadataTrack, + patchMetadataTrack, } = require('../controllers/metadataTrackController'); const { expressAuthorizationMiddleware } = require('../middlewares/authMiddlewares'); @@ -9,4 +10,8 @@ module.exports = { expressAuthorizationMiddleware, (req, res, next) => createMetadataTrack(req, res).catch(next), ], + 'metadataTrack#patchMetadataTrack': [ + expressAuthorizationMiddleware, + (req, res, next) => patchMetadataTrack(req, res).catch(next), + ], }; diff --git a/src/specs/api.v2.yaml b/src/specs/api.v2.yaml index dfac57d85..7e13980d6 100644 --- a/src/specs/api.v2.yaml +++ b/src/specs/api.v2.yaml @@ -493,6 +493,55 @@ paths: operationId: createMetadataTrack x-eov-operation-id: metadataTrack#createMetadataTrack x-eov-operation-handler: routes/metadataTrack + # requestBody: + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPSuccess' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPError' + '401': + description: The request lacks authentication credentials. + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPError' + '403': + description: Forbidden request for this user. + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPError' + '404': + description: Not found error. + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPError' + patch: + summary: Updates a metadata tracks properties + description: Updates a metadata tracks properties + operationId: patchMetadataTrack + x-eov-operation-id: metadataTrack#patchMetadataTrack + x-eov-operation-handler: routes/metadataTrack + requestBody: + content: + application/json: + schema: + type: object + properties: + key: + type: string + required: + - key + additionalProperties: false responses: '200': description: Success diff --git a/src/sql/migrations/20220304184711_schema.js b/src/sql/migrations/20220304184711_schema.js index 046392996..1cdc8a0d0 100644 --- a/src/sql/migrations/20220304184711_schema.js +++ b/src/sql/migrations/20220304184711_schema.js @@ -77,6 +77,8 @@ exports.up = async (knex) => { table.increments('id', { primaryKey: true }); table.uuid('experiment_id').references('experiment.id').onDelete('CASCADE').notNullable(); table.string('key'); + + table.unique(['experiment_id', 'key']); }); await knex.schema From 9a29c1e425206caa23f84d691c2d83808b1de4f1 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Wed, 27 Apr 2022 16:59:38 -0300 Subject: [PATCH 02/12] Add patch sample value in metadata track endpoint --- .../controllers/metadataTrackController.js | 15 ++++++ src/api.v2/model/MetadataTrack.js | 18 ++++++- src/api.v2/routes/metadataTrack.js | 5 ++ src/specs/api.v2.yaml | 52 +++++++++++++++++++ 4 files changed, 88 insertions(+), 2 deletions(-) diff --git a/src/api.v2/controllers/metadataTrackController.js b/src/api.v2/controllers/metadataTrackController.js index 0b08c7aac..6065774d5 100644 --- a/src/api.v2/controllers/metadataTrackController.js +++ b/src/api.v2/controllers/metadataTrackController.js @@ -38,7 +38,22 @@ const patchMetadataTrack = async (req, res) => { res.json(OK()); }; +const patchSampleInMetadataTrackValue = async (req, res) => { + const { + params: { experimentId, sampleId, metadataTrackKey }, + body: { value }, + } = req; + + logger.log(`Patching value of metadata track ${metadataTrackKey} in sample ${sampleId} in experiment ${experimentId}`); + + await new MetadataTrack().patchValueForSample(experimentId, sampleId, metadataTrackKey, value); + + logger.log(`Finished patching value of metadata track ${metadataTrackKey} in sample ${sampleId} in experiment ${experimentId}, changed to ${value}`); + res.json(OK()); +}; + module.exports = { createMetadataTrack, patchMetadataTrack, + patchSampleInMetadataTrackValue, }; diff --git a/src/api.v2/model/MetadataTrack.js b/src/api.v2/model/MetadataTrack.js index 04cf65c42..f1f79ea92 100644 --- a/src/api.v2/model/MetadataTrack.js +++ b/src/api.v2/model/MetadataTrack.js @@ -3,6 +3,7 @@ const BasicModel = require('./BasicModel'); const sqlClient = require('../../sql/sqlClient'); const tableNames = require('./tableNames'); +const { NotFoundError } = require('../../utils/responses'); const sampleFields = [ 'id', @@ -15,7 +16,7 @@ class MetadataTrack extends BasicModel { super(sql, tableNames.METADATA_TRACK, sampleFields); } - async createNewMetadataTrack(experimentId, metadataTrackKey) { + async createNewMetadataTrack(experimentId, key) { const sampleIds = await this.sql.select(['id']) .from(tableNames.SAMPLE) .where({ experiment_id: experimentId }); @@ -24,7 +25,7 @@ class MetadataTrack extends BasicModel { const response = await trx .insert({ experiment_id: experimentId, - key: metadataTrackKey, + key, }) .returning(['id']) .into(this.tableName); @@ -62,6 +63,19 @@ class MetadataTrack extends BasicModel { await this.sql(tableNames.SAMPLE_IN_METADATA_TRACK_MAP).insert(valuesToInsert); } + + async patchValueForSample(experimentId, sampleId, key, value) { + const [{ id }] = await this.find({ experiment_id: experimentId, key }); + + const result = await this.sql(tableNames.SAMPLE_IN_METADATA_TRACK_MAP) + .update({ value }) + .where({ metadata_track_id: id, sample_id: sampleId }) + .returning(['metadata_track_id']); + + if (result.length === 0) { + throw new NotFoundError(`Metadata track ${key} or sample ${sampleId} don't exist`); + } + } } module.exports = MetadataTrack; diff --git a/src/api.v2/routes/metadataTrack.js b/src/api.v2/routes/metadataTrack.js index 2fc305a2e..3529a964c 100644 --- a/src/api.v2/routes/metadataTrack.js +++ b/src/api.v2/routes/metadataTrack.js @@ -1,6 +1,7 @@ const { createMetadataTrack, patchMetadataTrack, + patchSampleInMetadataTrackValue, } = require('../controllers/metadataTrackController'); const { expressAuthorizationMiddleware } = require('../middlewares/authMiddlewares'); @@ -14,4 +15,8 @@ module.exports = { expressAuthorizationMiddleware, (req, res, next) => patchMetadataTrack(req, res).catch(next), ], + 'metadataTrack#patchSampleInMetadataTrackValue': [ + expressAuthorizationMiddleware, + (req, res, next) => patchSampleInMetadataTrackValue(req, res).catch(next), + ], }; diff --git a/src/specs/api.v2.yaml b/src/specs/api.v2.yaml index 7e13980d6..c8675c22b 100644 --- a/src/specs/api.v2.yaml +++ b/src/specs/api.v2.yaml @@ -433,6 +433,7 @@ paths: application/json: schema: $ref: '#/components/schemas/HTTPError' + '/experiments/{experimentId}/samples/position': put: summary: Update position for a sample @@ -574,6 +575,57 @@ paths: schema: $ref: '#/components/schemas/HTTPError' + '/experiments/{experimentId}/samples/{sampleId}/metadataTracks/{metadataTrackKey}': + patch: + summary: Updates the value that a sample has in a metadata track in particular + description: Updates a metadata tracks properties + operationId: patchMetadataTrack + x-eov-operation-id: metadataTrack#patchSampleInMetadataTrackValue + x-eov-operation-handler: routes/metadataTrack + requestBody: + content: + application/json: + schema: + type: object + properties: + value: + type: string + required: + - value + additionalProperties: false + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPSuccess' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPError' + '401': + description: The request lacks authentication credentials. + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPError' + '403': + description: Forbidden request for this user. + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPError' + '404': + description: Not found error. + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPError' + + components: schemas: CreateExperiment: From c3c542451d61934de437219386a6d05aab9305ba Mon Sep 17 00:00:00 2001 From: cosa65 Date: Wed, 27 Apr 2022 17:41:18 -0300 Subject: [PATCH 03/12] Improve description of create metadata track endpoint --- src/specs/api.v2.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/specs/api.v2.yaml b/src/specs/api.v2.yaml index c8675c22b..88323a2c5 100644 --- a/src/specs/api.v2.yaml +++ b/src/specs/api.v2.yaml @@ -490,11 +490,10 @@ paths: '/experiments/{experimentId}/metadataTracks/{metadataTrackKey}': post: summary: Creates a new metadata track - description: Creates a new metadata track + description: Creates a new metadata track, no requestBody required operationId: createMetadataTrack x-eov-operation-id: metadataTrack#createMetadataTrack x-eov-operation-handler: routes/metadataTrack - # requestBody: responses: '200': description: Success From 74f45aa1eb5a6706ca500f5cce74735f2a40d9d5 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Thu, 28 Apr 2022 10:25:56 -0300 Subject: [PATCH 04/12] Add delete metdadata track endpoint --- .../controllers/metadataTrackController.js | 17 +++++++++ src/api.v2/model/BasicModel.js | 7 ++++ src/api.v2/routes/metadataTrack.js | 5 +++ src/specs/api.v2.yaml | 38 ++++++++++++++++++- 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/api.v2/controllers/metadataTrackController.js b/src/api.v2/controllers/metadataTrackController.js index 6065774d5..50c6784a9 100644 --- a/src/api.v2/controllers/metadataTrackController.js +++ b/src/api.v2/controllers/metadataTrackController.js @@ -52,8 +52,25 @@ const patchSampleInMetadataTrackValue = async (req, res) => { res.json(OK()); }; +const deleteMetadataTrack = async (req, res) => { + const { + params: { experimentId, metadataTrackKey }, + } = req; + + logger.log(`Creating metadata track ${metadataTrackKey} in experiment ${experimentId}`); + + await new MetadataTrack().deleteAnyMatches( + { experiment_id: experimentId, key: metadataTrackKey }, + ); + + logger.log(`Finished creating metadata track ${metadataTrackKey} in experiment ${experimentId}`); + + res.json(OK()); +}; + module.exports = { createMetadataTrack, patchMetadataTrack, patchSampleInMetadataTrackValue, + deleteMetadataTrack, }; diff --git a/src/api.v2/model/BasicModel.js b/src/api.v2/model/BasicModel.js index ea0804918..1b8b8ef7a 100644 --- a/src/api.v2/model/BasicModel.js +++ b/src/api.v2/model/BasicModel.js @@ -74,6 +74,13 @@ class BasicModel { .where({ id }) .timeout(this.timeout); } + + deleteAnyMatches(filters) { + return this.sql.del() + .from(this.tableName) + .where(filters) + .timeout(this.timeout); + } } module.exports = BasicModel; diff --git a/src/api.v2/routes/metadataTrack.js b/src/api.v2/routes/metadataTrack.js index 3529a964c..d247f5fa1 100644 --- a/src/api.v2/routes/metadataTrack.js +++ b/src/api.v2/routes/metadataTrack.js @@ -2,6 +2,7 @@ const { createMetadataTrack, patchMetadataTrack, patchSampleInMetadataTrackValue, + deleteMetadataTrack, } = require('../controllers/metadataTrackController'); const { expressAuthorizationMiddleware } = require('../middlewares/authMiddlewares'); @@ -19,4 +20,8 @@ module.exports = { expressAuthorizationMiddleware, (req, res, next) => patchSampleInMetadataTrackValue(req, res).catch(next), ], + 'metadataTrack#deleteMetadataTrack': [ + expressAuthorizationMiddleware, + (req, res, next) => deleteMetadataTrack(req, res).catch(next), + ], }; diff --git a/src/specs/api.v2.yaml b/src/specs/api.v2.yaml index 88323a2c5..5864fbe20 100644 --- a/src/specs/api.v2.yaml +++ b/src/specs/api.v2.yaml @@ -573,7 +573,43 @@ paths: application/json: schema: $ref: '#/components/schemas/HTTPError' - + delete: + summary: Deletes a metadata track + description: Deletes a metadata track, no requestBody required + operationId: deleteMetadataTrack + x-eov-operation-id: metadataTrack#deleteMetadataTrack + x-eov-operation-handler: routes/metadataTrack + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPSuccess' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPError' + '401': + description: The request lacks authentication credentials. + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPError' + '403': + description: Forbidden request for this user. + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPError' + '404': + description: Not found error. + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPError' '/experiments/{experimentId}/samples/{sampleId}/metadataTracks/{metadataTrackKey}': patch: summary: Updates the value that a sample has in a metadata track in particular From 7f024e82464de5617854f3ecde0f258973f85463 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Thu, 28 Apr 2022 10:27:32 -0300 Subject: [PATCH 05/12] Add 403 response for endpoints of processing config --- src/specs/api.v2.yaml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/specs/api.v2.yaml b/src/specs/api.v2.yaml index 5864fbe20..5f033f9f3 100644 --- a/src/specs/api.v2.yaml +++ b/src/specs/api.v2.yaml @@ -86,7 +86,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/HTTPError' + $ref: '#/components/schemas/HTTPError' + '403': + description: Forbidden request for this user. + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPError' '404': description: Not found error. content: @@ -119,6 +125,12 @@ paths: application/json: schema: $ref: '#/components/schemas/HTTPError' + '403': + description: Forbidden request for this user. + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPError' '404': description: Not found error. content: From 511fa7a1b4292e69770b43660cdbe945f5c7962d Mon Sep 17 00:00:00 2001 From: cosa65 Date: Thu, 28 Apr 2022 15:17:52 -0300 Subject: [PATCH 06/12] Change destroy to delete in BasicModel and add a test for deleteAnyMatches --- src/api.v2/controllers/sampleController.js | 2 +- src/api.v2/model/BasicModel.js | 2 +- tests/api.v2/model/BasicModel.test.js | 18 ++++++++++++++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/api.v2/controllers/sampleController.js b/src/api.v2/controllers/sampleController.js index 916d941dd..f787e8597 100644 --- a/src/api.v2/controllers/sampleController.js +++ b/src/api.v2/controllers/sampleController.js @@ -52,7 +52,7 @@ const deleteSample = async (req, res) => { logger.log(`Deleting sample ${sampleId} from experiment ${experimentId}`); await sqlClient.get().transaction(async (trx) => { - await new Sample(trx).destroy(sampleId); + await new Sample(trx).delete(sampleId); await new Experiment(trx).deleteSample(experimentId, sampleId); }); diff --git a/src/api.v2/model/BasicModel.js b/src/api.v2/model/BasicModel.js index 1b8b8ef7a..9e3be5f33 100644 --- a/src/api.v2/model/BasicModel.js +++ b/src/api.v2/model/BasicModel.js @@ -68,7 +68,7 @@ class BasicModel { .timeout(this.timeout); } - destroy(id) { + delete(id) { return this.sql.del() .from(this.tableName) .where({ id }) diff --git a/tests/api.v2/model/BasicModel.test.js b/tests/api.v2/model/BasicModel.test.js index 813a61b07..be198c969 100644 --- a/tests/api.v2/model/BasicModel.test.js +++ b/tests/api.v2/model/BasicModel.test.js @@ -95,12 +95,26 @@ describe('model/BasicModel', () => { expect(mockSqlClient.timeout).toHaveBeenCalledWith(4000); }); - it('destroy works correctly', async () => { - await new BasicModel(mockSqlClient, mockTableName, ['id', 'name']).destroy('mockId'); + it('delete works correctly', async () => { + await new BasicModel(mockSqlClient, mockTableName, ['id', 'name']).delete('mockId'); expect(mockSqlClient.del).toHaveBeenCalled(); expect(mockSqlClient.from).toHaveBeenCalledWith(mockTableName); expect(mockSqlClient.where).toHaveBeenCalledWith({ id: 'mockId' }); expect(mockSqlClient.timeout).toHaveBeenCalledWith(4000); }); + + it('deleteAnyMatches works correctly', async () => { + const mockKey = 'aKey'; + const mockExperimentId = 'anExperimentId'; + + await new BasicModel(mockSqlClient, mockTableName, ['key', 'experiment_id', 'name']).deleteAnyMatches({ key: mockKey, experiment_id: mockExperimentId }); + + expect(mockSqlClient.del).toHaveBeenCalled(); + expect(mockSqlClient.from).toHaveBeenCalledWith(mockTableName); + expect(mockSqlClient.where).toHaveBeenCalledWith( + { key: mockKey, experiment_id: mockExperimentId }, + ); + expect(mockSqlClient.timeout).toHaveBeenCalledWith(4000); + }); }); From 08ba7134e247f70886ca10c26160bb6e99629f2b Mon Sep 17 00:00:00 2001 From: cosa65 Date: Fri, 29 Apr 2022 10:38:10 -0300 Subject: [PATCH 07/12] Set value in sample_in_metadata_track_map default to N.A. --- src/api.v2/model/MetadataTrack.js | 2 -- src/sql/migrations/20220304184711_schema.js | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/api.v2/model/MetadataTrack.js b/src/api.v2/model/MetadataTrack.js index f1f79ea92..968e96483 100644 --- a/src/api.v2/model/MetadataTrack.js +++ b/src/api.v2/model/MetadataTrack.js @@ -39,7 +39,6 @@ class MetadataTrack extends BasicModel { const valuesToInsert = sampleIds.map(({ id: sampleId }) => ({ metadata_track_id: metadataTrackId, sample_id: sampleId, - value: 'N.A.', })); await trx(tableNames.SAMPLE_IN_METADATA_TRACK_MAP).insert(valuesToInsert); @@ -58,7 +57,6 @@ class MetadataTrack extends BasicModel { const valuesToInsert = tracks.map(({ id }) => ({ metadata_track_id: id, sample_id: sampleId, - value: 'N.A.', })); await this.sql(tableNames.SAMPLE_IN_METADATA_TRACK_MAP).insert(valuesToInsert); diff --git a/src/sql/migrations/20220304184711_schema.js b/src/sql/migrations/20220304184711_schema.js index 5522ae099..500638414 100644 --- a/src/sql/migrations/20220304184711_schema.js +++ b/src/sql/migrations/20220304184711_schema.js @@ -130,7 +130,7 @@ exports.up = async (knex) => { .createTable('sample_in_metadata_track_map', (table) => { table.integer('metadata_track_id').references('metadata_track.id').onDelete('CASCADE').notNullable(); table.uuid('sample_id').references('sample.id').onDelete('CASCADE').notNullable(); - table.string('value').notNullable(); + table.string('value').defaultTo('N.A.'); table.primary(['metadata_track_id', 'sample_id']); }); From ca3741def5a8ac5acdd0886acc793d869047b219 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Fri, 29 Apr 2022 11:33:18 -0300 Subject: [PATCH 08/12] Add tests for MetadataTrack model --- tests/api.v2/model/BasicModel.test.js | 3 +- tests/api.v2/model/MetadataTrack.test.js | 50 ++++- .../__snapshots__/MetadataTrack.test.js.snap | 177 ++++++++++++++++++ 3 files changed, 226 insertions(+), 4 deletions(-) create mode 100644 tests/api.v2/model/__snapshots__/MetadataTrack.test.js.snap diff --git a/tests/api.v2/model/BasicModel.test.js b/tests/api.v2/model/BasicModel.test.js index be198c969..e8c8aeae1 100644 --- a/tests/api.v2/model/BasicModel.test.js +++ b/tests/api.v2/model/BasicModel.test.js @@ -108,7 +108,8 @@ describe('model/BasicModel', () => { const mockKey = 'aKey'; const mockExperimentId = 'anExperimentId'; - await new BasicModel(mockSqlClient, mockTableName, ['key', 'experiment_id', 'name']).deleteAnyMatches({ key: mockKey, experiment_id: mockExperimentId }); + await new BasicModel(mockSqlClient, mockTableName, ['key', 'experiment_id', 'name']) + .deleteAnyMatches({ key: mockKey, experiment_id: mockExperimentId }); expect(mockSqlClient.del).toHaveBeenCalled(); expect(mockSqlClient.from).toHaveBeenCalledWith(mockTableName); diff --git a/tests/api.v2/model/MetadataTrack.test.js b/tests/api.v2/model/MetadataTrack.test.js index f547f1e33..71ec203d1 100644 --- a/tests/api.v2/model/MetadataTrack.test.js +++ b/tests/api.v2/model/MetadataTrack.test.js @@ -1,17 +1,61 @@ // @ts-nocheck -const { mockSqlClient } = require('../mocks/getMockSqlClient')(); +const { mockSqlClient, mockTrx } = require('../mocks/getMockSqlClient')(); jest.mock('../../../src/sql/sqlClient', () => ({ get: jest.fn(() => mockSqlClient), })); const MetadataTrack = require('../../../src/api.v2/model/MetadataTrack'); +const tableNames = require('../../../src/api.v2/model/tableNames'); describe('model/userAccess', () => { beforeEach(() => { jest.clearAllMocks(); }); + it('createNewMetadataTrack works correctly when there are samples', async () => { + const experimentId = 'mockExperimentId'; + const key = 'mockKey'; + + mockSqlClient.where.mockReturnValueOnce([{ id: 'sampleId1' }, { id: 'sampleId2' }, { id: 'sampleId3' }, { id: 'sampleId4' }]); + mockTrx.into.mockReturnValueOnce([{ id: 'metadataTrackId1' }, { id: 'metadataTrackId2' }]); + + await new MetadataTrack().createNewMetadataTrack(experimentId, key); + + expect(mockSqlClient.transaction).toHaveBeenCalled(); + + expect(mockSqlClient.select.mock.calls).toMatchSnapshot('selectParams'); + expect(mockSqlClient.from.mock.calls).toMatchSnapshot('fromParams'); + expect(mockSqlClient.where.mock.calls).toMatchSnapshot('whereParams'); + + expect(mockTrx.insert.mock.calls).toMatchSnapshot('insertParams'); + expect(mockTrx.returning.mock.calls).toMatchSnapshot('returningParams'); + expect(mockTrx.into.mock.calls).toMatchSnapshot('intoParams'); + expect(mockTrx.insert.mock.calls).toMatchSnapshot('insertParams'); + expect(mockTrx).toHaveBeenCalledWith(tableNames.SAMPLE_IN_METADATA_TRACK_MAP); + }); + + it('createNewMetadataTrack skips insert into SAMPLE_IN_METADATA_TRACK_MAP when there are no samples', async () => { + const experimentId = 'mockExperimentId'; + const key = 'mockKey'; + + mockTrx.into.mockReturnValueOnce([]); + + await new MetadataTrack().createNewMetadataTrack(experimentId, key); + + expect(mockSqlClient.transaction).toHaveBeenCalled(); + + expect(mockSqlClient.select.mock.calls).toMatchSnapshot('selectParams'); + expect(mockSqlClient.from.mock.calls).toMatchSnapshot('fromParams'); + expect(mockSqlClient.where.mock.calls).toMatchSnapshot('whereParams'); + + expect(mockTrx.insert.mock.calls).toMatchSnapshot('insertParams'); + expect(mockTrx.returning.mock.calls).toMatchSnapshot('returningParams'); + expect(mockTrx.into.mock.calls).toMatchSnapshot('intoParams'); + expect(mockTrx.insert.mock.calls).toMatchSnapshot('insertParams'); + expect(mockTrx).not.toHaveBeenCalledWith(tableNames.SAMPLE_IN_METADATA_TRACK_MAP); + }); + it('createNewExperimentPermissions works correctly when experiment has metadata tracks', async () => { const mockExperimentId = 'mockExperimentId'; const mockSampleId = 'mockSampleId'; @@ -21,8 +65,8 @@ describe('model/userAccess', () => { await new MetadataTrack().createNewSampleValues(mockExperimentId, mockSampleId); expect(mockSqlClient.insert).toHaveBeenCalledWith([ - { metadata_track_id: 'track1Id', sample_id: 'mockSampleId', value: 'N.A.' }, - { metadata_track_id: 'track2Id', sample_id: 'mockSampleId', value: 'N.A.' }, + { metadata_track_id: 'track1Id', sample_id: 'mockSampleId' }, + { metadata_track_id: 'track2Id', sample_id: 'mockSampleId' }, ]); }); diff --git a/tests/api.v2/model/__snapshots__/MetadataTrack.test.js.snap b/tests/api.v2/model/__snapshots__/MetadataTrack.test.js.snap new file mode 100644 index 000000000..a750f67e1 --- /dev/null +++ b/tests/api.v2/model/__snapshots__/MetadataTrack.test.js.snap @@ -0,0 +1,177 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`model/userAccess createNewMetadataTrack skips insert into SAMPLE_IN_METADATA_TRACK_MAP when there are no samples: fromParams 1`] = ` +Array [ + Array [ + "sample", + ], +] +`; + +exports[`model/userAccess createNewMetadataTrack skips insert into SAMPLE_IN_METADATA_TRACK_MAP when there are no samples: insertParams 1`] = ` +Array [ + Array [ + Object { + "experiment_id": "mockExperimentId", + "key": "mockKey", + }, + ], +] +`; + +exports[`model/userAccess createNewMetadataTrack skips insert into SAMPLE_IN_METADATA_TRACK_MAP when there are no samples: insertParams 2`] = ` +Array [ + Array [ + Object { + "experiment_id": "mockExperimentId", + "key": "mockKey", + }, + ], +] +`; + +exports[`model/userAccess createNewMetadataTrack skips insert into SAMPLE_IN_METADATA_TRACK_MAP when there are no samples: intoParams 1`] = ` +Array [ + Array [ + "metadata_track", + ], +] +`; + +exports[`model/userAccess createNewMetadataTrack skips insert into SAMPLE_IN_METADATA_TRACK_MAP when there are no samples: returningParams 1`] = ` +Array [ + Array [ + Array [ + "id", + ], + ], +] +`; + +exports[`model/userAccess createNewMetadataTrack skips insert into SAMPLE_IN_METADATA_TRACK_MAP when there are no samples: selectParams 1`] = ` +Array [ + Array [ + Array [ + "id", + ], + ], +] +`; + +exports[`model/userAccess createNewMetadataTrack skips insert into SAMPLE_IN_METADATA_TRACK_MAP when there are no samples: whereParams 1`] = ` +Array [ + Array [ + Object { + "experiment_id": "mockExperimentId", + }, + ], +] +`; + +exports[`model/userAccess createNewMetadataTrack works correctly when there are samples: fromParams 1`] = ` +Array [ + Array [ + "sample", + ], +] +`; + +exports[`model/userAccess createNewMetadataTrack works correctly when there are samples: insertParams 1`] = ` +Array [ + Array [ + Object { + "experiment_id": "mockExperimentId", + "key": "mockKey", + }, + ], + Array [ + Array [ + Object { + "metadata_track_id": "metadataTrackId1", + "sample_id": "sampleId1", + }, + Object { + "metadata_track_id": "metadataTrackId1", + "sample_id": "sampleId2", + }, + Object { + "metadata_track_id": "metadataTrackId1", + "sample_id": "sampleId3", + }, + Object { + "metadata_track_id": "metadataTrackId1", + "sample_id": "sampleId4", + }, + ], + ], +] +`; + +exports[`model/userAccess createNewMetadataTrack works correctly when there are samples: insertParams 2`] = ` +Array [ + Array [ + Object { + "experiment_id": "mockExperimentId", + "key": "mockKey", + }, + ], + Array [ + Array [ + Object { + "metadata_track_id": "metadataTrackId1", + "sample_id": "sampleId1", + }, + Object { + "metadata_track_id": "metadataTrackId1", + "sample_id": "sampleId2", + }, + Object { + "metadata_track_id": "metadataTrackId1", + "sample_id": "sampleId3", + }, + Object { + "metadata_track_id": "metadataTrackId1", + "sample_id": "sampleId4", + }, + ], + ], +] +`; + +exports[`model/userAccess createNewMetadataTrack works correctly when there are samples: intoParams 1`] = ` +Array [ + Array [ + "metadata_track", + ], +] +`; + +exports[`model/userAccess createNewMetadataTrack works correctly when there are samples: returningParams 1`] = ` +Array [ + Array [ + Array [ + "id", + ], + ], +] +`; + +exports[`model/userAccess createNewMetadataTrack works correctly when there are samples: selectParams 1`] = ` +Array [ + Array [ + Array [ + "id", + ], + ], +] +`; + +exports[`model/userAccess createNewMetadataTrack works correctly when there are samples: whereParams 1`] = ` +Array [ + Array [ + Object { + "experiment_id": "mockExperimentId", + }, + ], +] +`; From 7a1d464b04d4bb5448fbcd1a43b08c925409d7a3 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Fri, 29 Apr 2022 11:43:35 -0300 Subject: [PATCH 09/12] Add tests for patchValueForSample --- tests/api.v2/model/MetadataTrack.test.js | 42 ++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/api.v2/model/MetadataTrack.test.js b/tests/api.v2/model/MetadataTrack.test.js index 71ec203d1..209482b64 100644 --- a/tests/api.v2/model/MetadataTrack.test.js +++ b/tests/api.v2/model/MetadataTrack.test.js @@ -6,6 +6,8 @@ jest.mock('../../../src/sql/sqlClient', () => ({ })); const MetadataTrack = require('../../../src/api.v2/model/MetadataTrack'); +const BasicModel = require('../../../src/api.v2/model/BasicModel'); + const tableNames = require('../../../src/api.v2/model/tableNames'); describe('model/userAccess', () => { @@ -56,6 +58,46 @@ describe('model/userAccess', () => { expect(mockTrx).not.toHaveBeenCalledWith(tableNames.SAMPLE_IN_METADATA_TRACK_MAP); }); + it('patchValueForSample works correctly', async () => { + const experimentId = 'mockExperimentId'; + const key = 'mockKey'; + const sampleId = 'mockSampleId'; + const value = 'mockValue'; + + const metadataTrackId = 'mockMetadataTrackId'; + + const mockFind = jest.spyOn(BasicModel.prototype, 'find') + .mockImplementationOnce(() => Promise.resolve([{ id: metadataTrackId }])); + + + + await new MetadataTrack().patchValueForSample(experimentId, sampleId, key, value); + + expect(mockFind).toHaveBeenCalledWith({ experiment_id: experimentId, key }); + + expect(mockSqlClient.update).toHaveBeenCalledWith({ value }); + expect(mockSqlClient.where).toHaveBeenCalledWith( + { metadata_track_id: metadataTrackId, sample_id: sampleId }, + ); + expect(mockSqlClient.returning).toHaveBeenCalledWith(['metadata_track_id']); + }); + + it('patchValueForSample throws if the track-sample map doesn\'t exist', async () => { + const experimentId = 'mockExperimentId'; + const key = 'mockKey'; + const sampleId = 'mockSampleId'; + const value = 'mockValue'; + + const mockFind = jest.spyOn(BasicModel.prototype, 'find') + .mockImplementationOnce(() => Promise.resolve([])); + + await expect( + new MetadataTrack().patchValueForSample(experimentId, sampleId, key, value), + ).rejects.toThrow(); + + expect(mockFind).toHaveBeenCalledWith({ experiment_id: experimentId, key }); + }); + it('createNewExperimentPermissions works correctly when experiment has metadata tracks', async () => { const mockExperimentId = 'mockExperimentId'; const mockSampleId = 'mockSampleId'; From e419102bee7b07cb94aa45c6c082e45e2e5f68a5 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Fri, 29 Apr 2022 12:09:42 -0300 Subject: [PATCH 10/12] Add tests for metadata track routes --- tests/api.v2/routes/metadataTrack.test.js | 158 ++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 tests/api.v2/routes/metadataTrack.test.js diff --git a/tests/api.v2/routes/metadataTrack.test.js b/tests/api.v2/routes/metadataTrack.test.js new file mode 100644 index 000000000..e88ec6fa6 --- /dev/null +++ b/tests/api.v2/routes/metadataTrack.test.js @@ -0,0 +1,158 @@ +// @ts-nocheck +const express = require('express'); +const request = require('supertest'); +const expressLoader = require('../../../src/loaders/express'); + +const { OK } = require('../../../src/utils/responses'); + +const metadataTrackController = require('../../../src/api.v2/controllers/metadataTrackController'); + +jest.mock('../../../src/api.v2/controllers/metadataTrackController', () => ({ + createMetadataTrack: jest.fn(), + patchMetadataTrack: jest.fn(), + deleteMetadataTrack: jest.fn(), + patchSampleInMetadataTrackValue: jest.fn(), +})); + +jest.mock('../../../src/api.v2/middlewares/authMiddlewares'); + +const experimentId = 'experimentId'; +const sampleId = 'sampleId'; +const metadataTrackKey = 'metadataTrackKey'; + +describe('tests for metadata track routes', () => { + let app = null; + + beforeEach(async () => { + const mockApp = await expressLoader(express()); + app = mockApp.app; + }); + + afterEach(() => { + /** + * Most important since b'coz of caching, the mocked implementations sometimes does not reset + */ + jest.resetModules(); + jest.restoreAllMocks(); + }); + + it('Creating a new metadata track works', async (done) => { + metadataTrackController.createMetadataTrack.mockImplementationOnce((req, res) => { + res.json(OK()); + return Promise.resolve(); + }); + + request(app) + .post(`/v2/experiments/${experimentId}/metadataTracks/${metadataTrackKey}`) + .send() + .expect(200) + .end((err) => { + if (err) { + return done(err); + } + // there is no point testing for the values of the response body + // - if something is wrong, the schema validator will catch it + return done(); + }); + }); + + it('Patching a metadata track works', async (done) => { + metadataTrackController.patchMetadataTrack.mockImplementationOnce((req, res) => { + res.json(OK()); + return Promise.resolve(); + }); + + request(app) + .patch(`/v2/experiments/${experimentId}/metadataTracks/${metadataTrackKey}`) + .send({ key: 'newMetadataTrackKey' }) + .expect(200) + .end((err) => { + if (err) { + return done(err); + } + // there is no point testing for the values of the response body + // - if something is wrong, the schema validator will catch it + return done(); + }); + }); + + it('Patching a metadata track with invalid body fails', async (done) => { + metadataTrackController.patchMetadataTrack.mockImplementationOnce((req, res) => { + res.json(OK()); + return Promise.resolve(); + }); + + request(app) + .patch(`/v2/experiments/${experimentId}/metadataTracks/${metadataTrackKey}`) + .send({ invalidKey: 'newMetadataTrackKey' }) + .expect(400) + .end((err) => { + if (err) { + return done(err); + } + // there is no point testing for the values of the response body + // - if something is wrong, the schema validator will catch it + return done(); + }); + }); + + it('Deleting a metadata track works', async (done) => { + metadataTrackController.deleteMetadataTrack.mockImplementationOnce((req, res) => { + res.json(OK()); + return Promise.resolve(); + }); + + request(app) + .delete(`/v2/experiments/${experimentId}/metadataTracks/${metadataTrackKey}`) + .send() + .expect(200) + .end((err) => { + if (err) { + return done(err); + } + // there is no point testing for the values of the response body + // - if something is wrong, the schema validator will catch it + return done(); + }); + }); + + it('Patching the value a sample has in a metadata track works', async (done) => { + metadataTrackController.patchSampleInMetadataTrackValue.mockImplementationOnce((req, res) => { + res.json(OK()); + return Promise.resolve(); + }); + + request(app) + .patch(`/v2/experiments/${experimentId}/samples/${sampleId}/metadataTracks/${metadataTrackKey}`) + .send({ value: 'mockNewValue' }) + .expect(200) + .end((err) => { + if (err) { + return done(err); + } + // there is no point testing for the values of the response body + // - if something is wrong, the schema validator will catch it + return done(); + }); + }); + + it('Patching the value a sample has in a metadata track with invalid request body fails', async (done) => { + metadataTrackController.patchSampleInMetadataTrackValue.mockImplementationOnce((req, res) => { + res.json(OK()); + return Promise.resolve(); + }); + + request(app) + .patch(`/v2/experiments/${experimentId}/samples/${sampleId}/metadataTracks/${metadataTrackKey}`) + .send({ invalidValue: 'mockNewValue' }) + .expect(400) + .end((err) => { + if (err) { + return done(err); + } + // there is no point testing for the values of the response body + // - if something is wrong, the schema validator will catch it + return done(); + }); + }); +}); From a3f5f9555fc068981c450cd705eec24d39201a89 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Fri, 29 Apr 2022 12:19:14 -0300 Subject: [PATCH 11/12] Fix tests in sampleController --- tests/api.v2/controllers/sampleController.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/api.v2/controllers/sampleController.test.js b/tests/api.v2/controllers/sampleController.test.js index 76782ff86..20a175f26 100644 --- a/tests/api.v2/controllers/sampleController.test.js +++ b/tests/api.v2/controllers/sampleController.test.js @@ -91,7 +91,7 @@ describe('sampleController', () => { it('deleteSample works correctly', async () => { const mockReq = { params: { experimentId: mockExperimentId, sampleId: mockSampleId } }; - sampleInstance.destroy.mockImplementationOnce(() => Promise.resolve()); + sampleInstance.delete.mockImplementationOnce(() => Promise.resolve()); experimentInstance.deleteSample.mockImplementationOnce(() => Promise.resolve()); await sampleController.deleteSample(mockReq, mockRes); @@ -106,7 +106,7 @@ describe('sampleController', () => { expect(Experiment).not.toHaveBeenCalledWith(mockSqlClient); expect(Sample).not.toHaveBeenCalledWith(mockSqlClient); - expect(sampleInstance.destroy).toHaveBeenCalledWith(mockSampleId); + expect(sampleInstance.delete).toHaveBeenCalledWith(mockSampleId); expect(experimentInstance.deleteSample).toHaveBeenCalledWith(mockExperimentId, mockSampleId); expect(mockRes.json).toHaveBeenCalledWith(OK()); From a5851666517bb0f830454477abbc14c70bc62848 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Fri, 29 Apr 2022 13:58:25 -0300 Subject: [PATCH 12/12] Change the syntax of delete to deleteById and deleteAnyMatches to delete so that they match better the naming patterns of other BasicModel functions --- .../controllers/metadataTrackController.js | 36 +++-- src/api.v2/controllers/sampleController.js | 2 +- src/api.v2/model/BasicModel.js | 6 +- src/api.v2/model/__mocks__/BasicModel.js | 3 +- src/api.v2/model/__mocks__/MetadataTrack.js | 2 + src/api.v2/routes/metadataTrack.js | 4 +- .../metadataTrackController.test.js | 148 ++++++++++++++++++ .../controllers/sampleController.test.js | 4 +- tests/api.v2/model/BasicModel.test.js | 8 +- tests/api.v2/routes/metadataTrack.test.js | 6 +- 10 files changed, 188 insertions(+), 31 deletions(-) create mode 100644 tests/api.v2/controllers/metadataTrackController.test.js diff --git a/src/api.v2/controllers/metadataTrackController.js b/src/api.v2/controllers/metadataTrackController.js index 50c6784a9..7af28de30 100644 --- a/src/api.v2/controllers/metadataTrackController.js +++ b/src/api.v2/controllers/metadataTrackController.js @@ -31,46 +31,50 @@ const patchMetadataTrack = async (req, res) => { .update({ experiment_id: experimentId, key: oldKey }, { key }); if (result.length === 0) { - throw new NotFoundError(`Metadata track ${oldKey} doesn't exist`); + throw new NotFoundError(`Metadata track ${oldKey} not found`); } logger.log(`Finished patching metadata track ${oldKey} in experiment ${experimentId}, changed to ${key}`); res.json(OK()); }; -const patchSampleInMetadataTrackValue = async (req, res) => { +const deleteMetadataTrack = async (req, res) => { const { - params: { experimentId, sampleId, metadataTrackKey }, - body: { value }, + params: { experimentId, metadataTrackKey }, } = req; - logger.log(`Patching value of metadata track ${metadataTrackKey} in sample ${sampleId} in experiment ${experimentId}`); + logger.log(`Creating metadata track ${metadataTrackKey} in experiment ${experimentId}`); - await new MetadataTrack().patchValueForSample(experimentId, sampleId, metadataTrackKey, value); + const result = await new MetadataTrack().delete( + { experiment_id: experimentId, key: metadataTrackKey }, + ); + + if (result.length === 0) { + throw new NotFoundError(`Metadata track ${metadataTrackKey} not found`); + } + + logger.log(`Finished creating metadata track ${metadataTrackKey} in experiment ${experimentId}`); - logger.log(`Finished patching value of metadata track ${metadataTrackKey} in sample ${sampleId} in experiment ${experimentId}, changed to ${value}`); res.json(OK()); }; -const deleteMetadataTrack = async (req, res) => { +const patchValueForSample = async (req, res) => { const { - params: { experimentId, metadataTrackKey }, + params: { experimentId, sampleId, metadataTrackKey }, + body: { value }, } = req; - logger.log(`Creating metadata track ${metadataTrackKey} in experiment ${experimentId}`); - - await new MetadataTrack().deleteAnyMatches( - { experiment_id: experimentId, key: metadataTrackKey }, - ); + logger.log(`Patching value of metadata track ${metadataTrackKey} in sample ${sampleId} in experiment ${experimentId}`); - logger.log(`Finished creating metadata track ${metadataTrackKey} in experiment ${experimentId}`); + await new MetadataTrack().patchValueForSample(experimentId, sampleId, metadataTrackKey, value); + logger.log(`Finished patching value of metadata track ${metadataTrackKey} in sample ${sampleId} in experiment ${experimentId}, changed to ${value}`); res.json(OK()); }; module.exports = { createMetadataTrack, patchMetadataTrack, - patchSampleInMetadataTrackValue, deleteMetadataTrack, + patchValueForSample, }; diff --git a/src/api.v2/controllers/sampleController.js b/src/api.v2/controllers/sampleController.js index f787e8597..4be01d1d1 100644 --- a/src/api.v2/controllers/sampleController.js +++ b/src/api.v2/controllers/sampleController.js @@ -52,7 +52,7 @@ const deleteSample = async (req, res) => { logger.log(`Deleting sample ${sampleId} from experiment ${experimentId}`); await sqlClient.get().transaction(async (trx) => { - await new Sample(trx).delete(sampleId); + await new Sample(trx).deleteById(sampleId); await new Experiment(trx).deleteSample(experimentId, sampleId); }); diff --git a/src/api.v2/model/BasicModel.js b/src/api.v2/model/BasicModel.js index 9e3be5f33..e685c58b1 100644 --- a/src/api.v2/model/BasicModel.js +++ b/src/api.v2/model/BasicModel.js @@ -68,17 +68,19 @@ class BasicModel { .timeout(this.timeout); } - delete(id) { + deleteById(id) { return this.sql.del() .from(this.tableName) .where({ id }) + .returning(this.selectableProps) .timeout(this.timeout); } - deleteAnyMatches(filters) { + delete(filters) { return this.sql.del() .from(this.tableName) .where(filters) + .returning(this.selectableProps) .timeout(this.timeout); } } diff --git a/src/api.v2/model/__mocks__/BasicModel.js b/src/api.v2/model/__mocks__/BasicModel.js index 802212abd..3e966e764 100644 --- a/src/api.v2/model/__mocks__/BasicModel.js +++ b/src/api.v2/model/__mocks__/BasicModel.js @@ -6,7 +6,8 @@ const stub = { findById: jest.fn(), update: jest.fn(), updateById: jest.fn(), - destroy: jest.fn(), + delete: jest.fn(), + deleteById: jest.fn(), }; const BasicModel = jest.fn().mockImplementation(() => stub); diff --git a/src/api.v2/model/__mocks__/MetadataTrack.js b/src/api.v2/model/__mocks__/MetadataTrack.js index 7f459c38c..ad4dc4b01 100644 --- a/src/api.v2/model/__mocks__/MetadataTrack.js +++ b/src/api.v2/model/__mocks__/MetadataTrack.js @@ -1,7 +1,9 @@ const BasicModel = require('./BasicModel')(); const stub = { + createNewMetadataTrack: jest.fn(), createNewSampleValues: jest.fn(), + patchValueForSample: jest.fn(), ...BasicModel, }; diff --git a/src/api.v2/routes/metadataTrack.js b/src/api.v2/routes/metadataTrack.js index d247f5fa1..370b32065 100644 --- a/src/api.v2/routes/metadataTrack.js +++ b/src/api.v2/routes/metadataTrack.js @@ -1,7 +1,7 @@ const { createMetadataTrack, patchMetadataTrack, - patchSampleInMetadataTrackValue, + patchValueForSample, deleteMetadataTrack, } = require('../controllers/metadataTrackController'); @@ -18,7 +18,7 @@ module.exports = { ], 'metadataTrack#patchSampleInMetadataTrackValue': [ expressAuthorizationMiddleware, - (req, res, next) => patchSampleInMetadataTrackValue(req, res).catch(next), + (req, res, next) => patchValueForSample(req, res).catch(next), ], 'metadataTrack#deleteMetadataTrack': [ expressAuthorizationMiddleware, diff --git a/tests/api.v2/controllers/metadataTrackController.test.js b/tests/api.v2/controllers/metadataTrackController.test.js new file mode 100644 index 000000000..fc5794b1b --- /dev/null +++ b/tests/api.v2/controllers/metadataTrackController.test.js @@ -0,0 +1,148 @@ +// @ts-nocheck +const metadataTrackController = require('../../../src/api.v2/controllers/metadataTrackController'); +const { OK, NotFoundError } = require('../../../src/utils/responses'); +const MetadataTrack = require('../../../src/api.v2/model/MetadataTrack'); + +const metadataTrackInstance = new MetadataTrack(); + +jest.mock('../../../src/api.v2/model/MetadataTrack'); + +const mockRes = { + json: jest.fn(), +}; + +describe('metadataTrackController', () => { + beforeEach(async () => { + jest.clearAllMocks(); + }); + + it('createMetadataTrack works correctly', async () => { + const experimentId = 'experimentId'; + const metadataTrackKey = 'metadataTrackKey'; + + const mockReq = { + params: { experimentId, metadataTrackKey }, + }; + + await metadataTrackController.createMetadataTrack(mockReq, mockRes); + + expect( + metadataTrackInstance.createNewMetadataTrack, + ).toHaveBeenCalledWith(experimentId, metadataTrackKey); + + // Response is ok + expect(mockRes.json).toHaveBeenCalledWith(OK()); + }); + + it('patchMetadataTrack works correctly', async () => { + const experimentId = 'experimentId'; + const oldMetadataTrackKey = 'oldKey'; + const newMetadataTrackKey = 'newKey'; + + const mockReq = { + params: { experimentId, metadataTrackKey: oldMetadataTrackKey }, + body: { key: newMetadataTrackKey }, + }; + + metadataTrackInstance.update.mockImplementationOnce( + () => [{ id: 1, experimentId, key: newMetadataTrackKey }], + ); + + await metadataTrackController.patchMetadataTrack(mockReq, mockRes); + + expect(metadataTrackInstance.update).toHaveBeenCalledWith( + { experiment_id: experimentId, key: oldMetadataTrackKey }, + { key: newMetadataTrackKey }, + ); + + // Response is ok + expect(mockRes.json).toHaveBeenCalledWith(OK()); + }); + + it('patchMetadataTrack throws if it didnt find a row to update', async () => { + const experimentId = 'experimentId'; + const oldMetadataTrackKey = 'oldKey'; + const newMetadataTrackKey = 'newKey'; + + const mockReq = { + params: { experimentId, metadataTrackKey: oldMetadataTrackKey }, + body: { key: newMetadataTrackKey }, + }; + + metadataTrackInstance.update.mockImplementationOnce(() => []); + + await expect( + metadataTrackController.patchMetadataTrack(mockReq, mockRes), + ).rejects.toThrow( + new NotFoundError(`Metadata track ${oldMetadataTrackKey} not found`), + ); + + // Response is not generated in controller + expect(mockRes.json).not.toHaveBeenCalled(); + }); + + it('deleteMetadataTrack works correctly', async () => { + const experimentId = 'experimentId'; + const metadataTrackKey = 'key'; + + const mockReq = { params: { experimentId, metadataTrackKey } }; + + metadataTrackInstance.delete.mockImplementationOnce( + () => [{ id: 1, experimentId, key: metadataTrackKey }], + ); + + await metadataTrackController.deleteMetadataTrack(mockReq, mockRes); + + expect(metadataTrackInstance.delete).toHaveBeenCalledWith( + { experiment_id: experimentId, key: metadataTrackKey }, + ); + + // Response is ok + expect(mockRes.json).toHaveBeenCalledWith(OK()); + }); + + it('deleteMetadataTrack throws if it didnt find a row to delete', async () => { + const experimentId = 'experimentId'; + const metadataTrackKey = 'key'; + + const mockReq = { params: { experimentId, metadataTrackKey } }; + + metadataTrackInstance.delete.mockImplementationOnce(() => []); + + await expect( + metadataTrackController.deleteMetadataTrack(mockReq, mockRes), + ).rejects.toThrow( + new NotFoundError(`Metadata track ${metadataTrackKey} not found`), + ); + + expect(metadataTrackInstance.delete).toHaveBeenCalledWith( + { experiment_id: experimentId, key: metadataTrackKey }, + ); + + // Response is not generated in controller + expect(mockRes.json).not.toHaveBeenCalledWith(OK()); + }); + + it('patchSampleInMetadataTrackValue works correctly', async () => { + const experimentId = 'experimentId'; + const metadataTrackKey = 'key'; + const sampleId = 'sampleId'; + const value = 'value'; + + const mockReq = { + params: { experimentId, sampleId, metadataTrackKey }, + body: { value }, + }; + + metadataTrackInstance.patchValueForSample.mockImplementationOnce(() => Promise.resolve()); + + await metadataTrackController.patchValueForSample(mockReq, mockRes); + + expect(metadataTrackInstance.patchValueForSample).toHaveBeenCalledWith( + experimentId, sampleId, metadataTrackKey, value, + ); + + // Response is ok + expect(mockRes.json).toHaveBeenCalledWith(OK()); + }); +}); diff --git a/tests/api.v2/controllers/sampleController.test.js b/tests/api.v2/controllers/sampleController.test.js index 20a175f26..bd12ce578 100644 --- a/tests/api.v2/controllers/sampleController.test.js +++ b/tests/api.v2/controllers/sampleController.test.js @@ -91,7 +91,7 @@ describe('sampleController', () => { it('deleteSample works correctly', async () => { const mockReq = { params: { experimentId: mockExperimentId, sampleId: mockSampleId } }; - sampleInstance.delete.mockImplementationOnce(() => Promise.resolve()); + sampleInstance.deleteById.mockImplementationOnce(() => Promise.resolve()); experimentInstance.deleteSample.mockImplementationOnce(() => Promise.resolve()); await sampleController.deleteSample(mockReq, mockRes); @@ -106,7 +106,7 @@ describe('sampleController', () => { expect(Experiment).not.toHaveBeenCalledWith(mockSqlClient); expect(Sample).not.toHaveBeenCalledWith(mockSqlClient); - expect(sampleInstance.delete).toHaveBeenCalledWith(mockSampleId); + expect(sampleInstance.deleteById).toHaveBeenCalledWith(mockSampleId); expect(experimentInstance.deleteSample).toHaveBeenCalledWith(mockExperimentId, mockSampleId); expect(mockRes.json).toHaveBeenCalledWith(OK()); diff --git a/tests/api.v2/model/BasicModel.test.js b/tests/api.v2/model/BasicModel.test.js index e8c8aeae1..28fe5c8c8 100644 --- a/tests/api.v2/model/BasicModel.test.js +++ b/tests/api.v2/model/BasicModel.test.js @@ -95,8 +95,8 @@ describe('model/BasicModel', () => { expect(mockSqlClient.timeout).toHaveBeenCalledWith(4000); }); - it('delete works correctly', async () => { - await new BasicModel(mockSqlClient, mockTableName, ['id', 'name']).delete('mockId'); + it('deleteById works correctly', async () => { + await new BasicModel(mockSqlClient, mockTableName, ['id', 'name']).deleteById('mockId'); expect(mockSqlClient.del).toHaveBeenCalled(); expect(mockSqlClient.from).toHaveBeenCalledWith(mockTableName); @@ -104,12 +104,12 @@ describe('model/BasicModel', () => { expect(mockSqlClient.timeout).toHaveBeenCalledWith(4000); }); - it('deleteAnyMatches works correctly', async () => { + it('delete works correctly', async () => { const mockKey = 'aKey'; const mockExperimentId = 'anExperimentId'; await new BasicModel(mockSqlClient, mockTableName, ['key', 'experiment_id', 'name']) - .deleteAnyMatches({ key: mockKey, experiment_id: mockExperimentId }); + .delete({ key: mockKey, experiment_id: mockExperimentId }); expect(mockSqlClient.del).toHaveBeenCalled(); expect(mockSqlClient.from).toHaveBeenCalledWith(mockTableName); diff --git a/tests/api.v2/routes/metadataTrack.test.js b/tests/api.v2/routes/metadataTrack.test.js index e88ec6fa6..6b78da7b5 100644 --- a/tests/api.v2/routes/metadataTrack.test.js +++ b/tests/api.v2/routes/metadataTrack.test.js @@ -11,7 +11,7 @@ jest.mock('../../../src/api.v2/controllers/metadataTrackController', () => ({ createMetadataTrack: jest.fn(), patchMetadataTrack: jest.fn(), deleteMetadataTrack: jest.fn(), - patchSampleInMetadataTrackValue: jest.fn(), + patchValueForSample: jest.fn(), })); jest.mock('../../../src/api.v2/middlewares/authMiddlewares'); @@ -117,7 +117,7 @@ describe('tests for metadata track routes', () => { }); it('Patching the value a sample has in a metadata track works', async (done) => { - metadataTrackController.patchSampleInMetadataTrackValue.mockImplementationOnce((req, res) => { + metadataTrackController.patchValueForSample.mockImplementationOnce((req, res) => { res.json(OK()); return Promise.resolve(); }); @@ -137,7 +137,7 @@ describe('tests for metadata track routes', () => { }); it('Patching the value a sample has in a metadata track with invalid request body fails', async (done) => { - metadataTrackController.patchSampleInMetadataTrackValue.mockImplementationOnce((req, res) => { + metadataTrackController.patchValueForSample.mockImplementationOnce((req, res) => { res.json(OK()); return Promise.resolve(); });