From a15dcb000db414ca4d00f286ec5b5e94f0aec42a Mon Sep 17 00:00:00 2001 From: Sadiq Khoja Date: Tue, 3 Dec 2024 13:54:22 -0500 Subject: [PATCH] Improves #794: Update error message on duplicate submission when original is soft deleted --- lib/resources/submissions.js | 11 +++++++++- lib/util/problem.js | 4 +++- test/integration/api/submissions.js | 34 +++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/lib/resources/submissions.js b/lib/resources/submissions.js index 69fa97821..5b327e08d 100644 --- a/lib/resources/submissions.js +++ b/lib/resources/submissions.js @@ -127,7 +127,16 @@ module.exports = (service, endpoint) => { return Promise.all([ Submissions.createNew(partial, form, query.deviceID, userAgent), Forms.getBinaryFields(form.def.id) - ]).then(([ saved, binaryFields ]) => SubmissionAttachments.create(saved, form, binaryFields, files)); + ]) + .then(([ saved, binaryFields ]) => SubmissionAttachments.create(saved, form, binaryFields, files)) + .catch((error) => { + // This is only true when submission is soft deleted, if it is hard deleted then there will no error + // and if it is not deleted then `extant` will be not null and this block will not execute. + if (error.isProblem === true && error.problemCode === 409.3) { + throw Problem.user.duplicateSubmission(); + } + throw error; + }); }); }) .then(always(createdMessage({ message: 'full submission upload was successful!' })))))); diff --git a/lib/util/problem.js b/lib/util/problem.js index 4ed87ac66..3082bfcdf 100644 --- a/lib/util/problem.js +++ b/lib/util/problem.js @@ -221,7 +221,9 @@ const problems = { entityVersionConflict: problem(409.15, ({ current, provided }) => `Current version of the Entity is '${current}' and you provided '${provided}'. Please correct the version number or pass '?force=true' in the URL to forcefully update the Entity.`), - datasetNameConflict: problem(409.16, ({ current, provided }) => `A dataset named '${current}' exists and you provided '${provided}' with the same name but different capitalization.`) + datasetNameConflict: problem(409.16, ({ current, provided }) => `A dataset named '${current}' exists and you provided '${provided}' with the same name but different capitalization.`), + + duplicateSubmission: problem(409.18, () => `This submission has been deleted. You may not resubmit it.`) }, internal: { diff --git a/test/integration/api/submissions.js b/test/integration/api/submissions.js index a031bc77a..770f6dc45 100644 --- a/test/integration/api/submissions.js +++ b/test/integration/api/submissions.js @@ -572,6 +572,26 @@ describe('api: /submission', () => { .then(({ body }) => { body.toString('utf8').should.equal('this is test file one'); }) ]))))); + it('should reject resubmission of soft-deleted submission', testService(async (service) => { + const asAlice = await service.login('alice'); + + await asAlice.post('/v1/projects/1/submission') + .set('X-OpenRosa-Version', '1.0') + .attach('xml_submission_file', Buffer.from(testData.instances.simple.one), { filename: 'data.xml' }) + .expect(201); + + await asAlice.delete('/v1/projects/1/forms/simple/submissions/one') + .expect(200); + + await asAlice.post('/v1/projects/1/submission') + .set('X-OpenRosa-Version', '1.0') + .attach('xml_submission_file', Buffer.from(testData.instances.simple.one), { filename: 'data.xml' }) + .expect(409) + .then(({ text }) => { + text.should.match(/This submission has been deleted. You may not resubmit it./); + }); + })); + context('versioning', () => { it('should reject if the deprecatedId is not known', testService((service) => service.login('alice', (asAlice) => @@ -1153,6 +1173,20 @@ describe('api: /forms/:id/submissions', () => { ]); }) ]))))); + + it('should reject duplicate submissions', testService(async (service) => { + const asAlice = await service.login('alice'); + + await asAlice.post('/v1/projects/1/forms/simple/submissions') + .send(testData.instances.simple.one) + .set('Content-Type', 'text/xml') + .expect(200); + + await asAlice.post('/v1/projects/1/forms/simple/submissions') + .send(testData.instances.simple.one) + .set('Content-Type', 'text/xml') + .expect(409); + })); }); describe('[draft] POST', () => {