From f507c451a12f800f3f2a84de772902c2c39ca731 Mon Sep 17 00:00:00 2001 From: Kathleen Tuite Date: Thu, 14 Sep 2023 16:24:20 -0700 Subject: [PATCH] Disallow entity creation via API for unpublished datasets --- lib/model/query/datasets.js | 6 +++--- lib/resources/entities.js | 4 ++-- test/integration/api/datasets.js | 34 ++++++++++++++++++++++++++++++++ test/integration/api/entities.js | 19 ++++++++++++++++++ 4 files changed, 58 insertions(+), 5 deletions(-) diff --git a/lib/model/query/datasets.js b/lib/model/query/datasets.js index db999eefa..9ac92cdb8 100644 --- a/lib/model/query/datasets.js +++ b/lib/model/query/datasets.js @@ -88,7 +88,7 @@ const createOrMerge = (name, form, fields) => async ({ one, Actees, Datasets, Pr // Provision acteeId if necessary. // Need to check for existing dataset, and if not found, need to also // fetch the project since the dataset will be an actee child of the project. - const existingDataset = await Datasets.get(projectId, name); + const existingDataset = await Datasets.get(projectId, name, false); const acteeId = existingDataset.isDefined() ? existingDataset.get().acteeId : await Projects.getById(projectId).then((o) => o.get()) @@ -189,7 +189,7 @@ publishIfExists.audit.withResult = true; //////////////////////////////////////////////////////////////////////////// // DATASET GETTERS -const _get = extender(Dataset)(Dataset.Extended)((fields, extend, options, publishedOnly = false, actorId) => sql` +const _get = extender(Dataset)(Dataset.Extended)((fields, extend, options, publishedOnly, actorId) => sql` SELECT ${fields} FROM datasets ${extend|| sql` LEFT JOIN ( @@ -224,7 +224,7 @@ const getList = (projectId, options = QueryOptions.none) => ({ all }) => _get(all, options.withCondition({ projectId }), true); // Get a dataset by it's project id and name. Commonly used in a resource. -const get = (projectId, name, publishedOnly = false, extended = false) => ({ maybeOne }) => { +const get = (projectId, name, publishedOnly, extended = false) => ({ maybeOne }) => { const options = extended ? QueryOptions.extended : QueryOptions.none; return _get(maybeOne, options.withCondition({ projectId, name }), publishedOnly); }; diff --git a/lib/resources/entities.js b/lib/resources/entities.js index 7e6e2ac28..2138ffe77 100644 --- a/lib/resources/entities.js +++ b/lib/resources/entities.js @@ -79,7 +79,7 @@ module.exports = (service, endpoint) => { service.post('/projects/:id/datasets/:name/entities', endpoint(async ({ Datasets, Entities }, { auth, body, params, userAgent }) => { - const dataset = await Datasets.get(params.id, params.name).then(getOrNotFound); + const dataset = await Datasets.get(params.id, params.name, true).then(getOrNotFound); await auth.canOrReject('entity.create', dataset); @@ -96,7 +96,7 @@ module.exports = (service, endpoint) => { service.patch('/projects/:id/datasets/:name/entities/:uuid', endpoint(async ({ Datasets, Entities }, { auth, body, params, query, userAgent, headers }) => { - const dataset = await Datasets.get(params.id, params.name).then(getOrNotFound); + const dataset = await Datasets.get(params.id, params.name, true).then(getOrNotFound); await auth.canOrReject('entity.update', dataset); diff --git a/test/integration/api/datasets.js b/test/integration/api/datasets.js index 106808f81..e17e9097c 100644 --- a/test/integration/api/datasets.js +++ b/test/integration/api/datasets.js @@ -715,6 +715,40 @@ describe('datasets and entities', () => { }); })); + it('should return dataset properties from multiple forms where later form publishes dataset', testService(async (service) => { + const asAlice = await service.login('alice'); + + await asAlice.post('/v1/projects/1/forms') + .send(testData.forms.multiPropertyEntity) + .set('Content-Type', 'application/xml') + .expect(200); + + await asAlice.post('/v1/projects/1/forms?publish=true') + .send(testData.forms.multiPropertyEntity + .replace('multiPropertyEntity', 'multiPropertyEntity2') + .replace('b_q1', 'f_q1') + .replace('d_q2', 'e_q2')) + .set('Content-Type', 'application/xml') + .expect(200); + + await asAlice.post('/v1/projects/1/forms/multiPropertyEntity/draft/publish'); + + await asAlice.get('/v1/projects/1/datasets/foo') + .expect(200) + .then(({ body }) => { + const { properties } = body; + properties.map((p) => p.name) + .should.be.eql([ + 'f_q1', + 'e_q2', + 'a_q3', + 'c_q4', + 'b_q1', + 'd_q2', + ]); + }); + })); + it('should return dataset properties from multiple forms including updated form with updated schema', testService(async (service) => { const asAlice = await service.login('alice'); diff --git a/test/integration/api/entities.js b/test/integration/api/entities.js index e800bbca8..dbba95391 100644 --- a/test/integration/api/entities.js +++ b/test/integration/api/entities.js @@ -715,6 +715,25 @@ describe('Entities API', () => { }); })); + it('should reject creating new entity if dataset not yet published', testService(async (service) => { + const asAlice = await service.login('alice'); + + await asAlice.post('/v1/projects/1/forms') + .send(testData.forms.simpleEntity) + .expect(200); + + await asAlice.get('/v1/projects/1/datasets/people') + .expect(404); + + await asAlice.post('/v1/projects/1/datasets/people/entities') + .send({ + uuid: '12345678-1234-4123-8234-111111111aaa', + label: 'Johnny Doe', + data: {} + }) + .expect(404); + })); + it('should create an Entity', testDataset(async (service) => { const asAlice = await service.login('alice');