From 5ddb7ad63eca11dceac7d579491bbd7786f49ebc Mon Sep 17 00:00:00 2001 From: Kathleen Tuite Date: Thu, 29 Aug 2024 16:45:12 -0700 Subject: [PATCH] Allow property name from deleted form to be used in entity list --- lib/model/query/datasets.js | 11 +++++- test/integration/api/datasets.js | 66 ++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/lib/model/query/datasets.js b/lib/model/query/datasets.js index 852c095fc..9c337f4af 100644 --- a/lib/model/query/datasets.js +++ b/lib/model/query/datasets.js @@ -179,11 +179,18 @@ createPublishedDataset.audit.withResult = true; const _createPublishedProperty = (property) => sql` INSERT INTO ds_properties ("name", "datasetId", "publishedAt") VALUES (${property.name}, ${property.datasetId}, clock_timestamp()) +ON CONFLICT ("name", "datasetId") +DO UPDATE SET "publishedAt" = clock_timestamp() +WHERE ds_properties."publishedAt" IS NULL RETURNING *`; // eslint-disable-next-line no-unused-vars -const createPublishedProperty = (property, dataset) => ({ one }) => - one(_createPublishedProperty(property)); +const createPublishedProperty = (property, dataset) => async ({ all }) => { + const result = await all(_createPublishedProperty(property)); + if (result.length === 0) + throw Problem.user.uniquenessViolation({ table: 'ds_properties', fields: [ 'name', 'datasetId' ], values: [ property.name, dataset.id ] }); + return result[0]; +}; createPublishedProperty.audit = (property, dataset) => (log) => log('dataset.update', dataset, { properties: [property.name] }); diff --git a/test/integration/api/datasets.js b/test/integration/api/datasets.js index b115002fb..b854a5de3 100644 --- a/test/integration/api/datasets.js +++ b/test/integration/api/datasets.js @@ -409,6 +409,34 @@ describe('datasets and entities', () => { }); })); + it('should reject if creating a dataset property that already exists', testService(async (service) => { + const asAlice = await service.login('alice'); + + await asAlice.post('/v1/projects/1/datasets') + .send({ + name: 'trees' + }) + .expect(200); + + // Create a property + await asAlice.post('/v1/projects/1/datasets/trees/properties') + .send({ + name: 'height' + }) + .expect(200); + + // Second time should fail + await asAlice.post('/v1/projects/1/datasets/trees/properties') + .send({ + name: 'height' + }) + .expect(409) + .then(({ body }) => { + body.code.should.equal(409.3); + body.message.should.startWith('A resource already exists with name,datasetId value(s) of height'); + }); + })); + it('should log an event for creating a new dataset property', testService(async (service) => { const asAlice = await service.login('alice'); @@ -434,6 +462,44 @@ describe('datasets and entities', () => { logs[0].details.properties.should.eql([ 'circumference' ]); }); })); + + it('should allow property name from a deleted draft form be used', testService(async (service) => { + const asAlice = await service.login('alice'); + + // Create a draft dataset + await asAlice.post('/v1/projects/1/forms') + .send(testData.forms.simpleEntity) + .set('Content-Type', 'application/xml') + .expect(200); + + // Delete the draft form + await asAlice.delete('/v1/projects/1/forms/simpleEntity') + .expect(200); + + // Create the dataset with the same name as the dataset in the deleted form + await asAlice.post('/v1/projects/1/datasets') + .send({ name: 'people' }) + .expect(200); + + // Create a property with a name that wasn't originally in the dataset in the form + await asAlice.post('/v1/projects/1/datasets/people/properties') + .send({ name: 'nickname' }) + .expect(200); + + // Create a property with a name that was in the dataset in the form + await asAlice.post('/v1/projects/1/datasets/people/properties') + .send({ name: 'age' }) + .expect(200); + + // Re-creating an existing property should result in an error + await asAlice.post('/v1/projects/1/datasets/people/properties') + .send({ name: 'age' }) + .expect(409) + .then(({ body }) => { + body.code.should.equal(409.3); + body.message.should.startWith('A resource already exists with name,datasetId value(s) of age'); + }); + })); }); });