diff --git a/lib/model/query/entities.js b/lib/model/query/entities.js index 8a4667231..015a276e5 100644 --- a/lib/model/query/entities.js +++ b/lib/model/query/entities.js @@ -219,7 +219,21 @@ const _updateEntity = (dataset, entityData, submissionId, submissionDef, submiss // Figure out the intended baseVersion // If this is an offline update with a branchId, the baseVersion value is local to that offline context. - const baseEntityDef = await Entities._computeBaseVersion(event.id, dataset, clientEntity, submissionDef, forceOutOfOrderProcessing); + let baseEntityDef; + + // Try computing base version. + // But if there is a 404.8 not found error, double-check if the entity never existed or was deleted. + try { + baseEntityDef = await Entities._computeBaseVersion(event.id, dataset, clientEntity, submissionDef, forceOutOfOrderProcessing); + } catch (err) { + if (err.problemCode === 404.8) { + // Look up deleted entity by passing deleted as option argData + const deletedEntity = await Entities.getById(dataset.id, clientEntity.uuid, new QueryOptions({ argData: { deleted: 'true' } })); + if (deletedEntity.isDefined()) + throw Problem.user.entityDeleted({ entityUuid: clientEntity.uuid }); + } + throw err; + } // If baseEntityVersion is null, we held a submission and will stop processing now. if (baseEntityDef == null) diff --git a/lib/util/problem.js b/lib/util/problem.js index 76fde7b55..c5c99c61c 100644 --- a/lib/util/problem.js +++ b/lib/util/problem.js @@ -166,6 +166,9 @@ const problems = { // entity base version specified in submission does not exist entityVersionNotFound: problem(404.9, ({ baseVersion, entityUuid, datasetName }) => `Base version (${baseVersion}) does not exist for entity UUID (${entityUuid}) in dataset (${datasetName}).`), + // entity has been deleted + entityDeleted: problem(404.11, ({ entityUuid }) => `The entity with UUID (${entityUuid}) has been deleted.`), + // { allowed: [ acceptable formats ], got } unacceptableFormat: problem(406.1, ({ allowed }) => `Requested format not acceptable; this resource allows: ${allowed.join()}.`), diff --git a/test/integration/api/offline-entities.js b/test/integration/api/offline-entities.js index 17650a7f8..0cb2baf5f 100644 --- a/test/integration/api/offline-entities.js +++ b/test/integration/api/offline-entities.js @@ -1195,7 +1195,42 @@ describe('Offline Entities', () => { }); })); - // TODO: check deleted entity + it('should not process a submission for an entity that has been soft-deleted', testOfflineEntities(async (service, container) => { + const asAlice = await service.login('alice'); + const branchId = uuid(); + + // Send the first submission, which will be held in the backlog because the base version is high + await asAlice.post('/v1/projects/1/forms/offlineEntity/submissions') + .send(testData.instances.offlineEntity.one + .replace('branchId=""', `branchId="${branchId}"`) + .replace('baseVersion="1"', 'baseVersion="2"') + ) + .set('Content-Type', 'application/xml') + .expect(200); + + await exhaust(container); + + let backlogCount = await container.oneFirst(sql`select count(*) from entity_submission_backlog`); + backlogCount.should.equal(1); + + // Soft-delete the entity + await asAlice.delete('/v1/projects/1/datasets/people/entities/12345678-1234-4123-8234-123456789abc'); + + // Process the backlog (count will be 1 but update should not be applied to entity) + const processedCount = await container.Entities.processBacklog(true); + processedCount.should.equal(1); + + // Check that the backlog count is now 0 + backlogCount = await container.oneFirst(sql`select count(*) from entity_submission_backlog`); + backlogCount.should.equal(0); + + // Check for an entity error on the submission + await asAlice.get('/v1/projects/1/forms/offlineEntity/submissions/one/audits') + .expect(200) + .then(({ body }) => { + body[0].details.errorMessage.should.eql('The entity with UUID (12345678-1234-4123-8234-123456789abc) has been deleted.'); + }); + })); }); }); });