diff --git a/lib/model/migrations/20241010-01-schedule-entity-form-upgrade.js b/lib/model/migrations/20241010-01-schedule-entity-form-upgrade.js new file mode 100644 index 000000000..39d31400c --- /dev/null +++ b/lib/model/migrations/20241010-01-schedule-entity-form-upgrade.js @@ -0,0 +1,22 @@ +// Copyright 2024 ODK Central Developers +// See the NOTICE file at the top-level directory of this distribution and at +// https://github.com/getodk/central-backend/blob/master/NOTICE. +// This file is part of ODK Central. It is subject to the license terms in +// the LICENSE file found in the top-level directory of this distribution and at +// https://www.apache.org/licenses/LICENSE-2.0. No part of ODK Central, +// including this file, may be copied, modified, propagated, or distributed +// except according to the terms contained in the LICENSE file. + +const up = (db) => db.raw(` + INSERT INTO audits ("action", "acteeId") + SELECT 'upgrade.process.form.entities_version', f."acteeId" + FROM forms f + JOIN form_defs fd ON f."id" = fd."formId" + JOIN dataset_form_defs dfd ON fd."id" = dfd."formDefId" + WHERE dfd."actions" @> '["update"]' + GROUP BY f."acteeId"; +`); + +const down = () => {}; + +module.exports = { up, down }; diff --git a/test/integration/other/migrations.js b/test/integration/other/migrations.js index bd5da0fa1..d5302e90d 100644 --- a/test/integration/other/migrations.js +++ b/test/integration/other/migrations.js @@ -1054,4 +1054,83 @@ testMigration('20240914-02-remove-orphaned-client-audits.js', () => { blobCount = await container.oneFirst(sql`select count(*) from blobs`); blobCount.should.equal(1); })); + + testMigration('20241010-01-schedule-entity-form-upgrade.js', () => { + it('should schedule entity forms with spec version 2023.1.0 for upgrade to 2024.1.0', testServiceFullTrx(async (service, container) => { + await populateUsers(container); + await populateForms(container); + + const asAlice = await service.login('alice'); + + // Upload one form with multiple versions + await asAlice.post('/v1/projects/1/forms?publish=true') + .send(testData.forms.updateEntity) + .expect(200); + + await asAlice.post('/v1/projects/1/forms/updateEntity/draft') + .send(testData.forms.updateEntity.replace('orx:version="1.0"', 'orx:version="2.0"')) + .set('Content-Type', 'text/xml') + .expect(200); + + await asAlice.post('/v1/projects/1/forms/updateEntity/draft/publish'); + + await asAlice.post('/v1/projects/1/forms/updateEntity/draft') + .send(testData.forms.updateEntity.replace('orx:version="1.0"', 'orx:version="3.0"')) + .set('Content-Type', 'text/xml') + .expect(200); + + // Upload another form that needs updating + await asAlice.post('/v1/projects/1/forms') + .send(testData.forms.updateEntity.replace('id="updateEntity"', 'id="updateEntity2"')) + .expect(200); + + // Upload an entity form that doesn't really need updating but does have 'update' in the actions column + await asAlice.post('/v1/projects/1/forms') + .send(testData.forms.offlineEntity) + .expect(200); + + // Upload an entity form that does not need updating + await asAlice.post('/v1/projects/1/forms') + .send(testData.forms.simpleEntity) + .expect(200); + + // Mark forms for upgrade + await up(); + + // Check: 2 forms that need upgradin and 1 2024.1 form that it wont harm to run through the worker + const audits = await container.oneFirst(sql`select count(*) from audits where action = 'upgrade.process.form.entities_version'`); + audits.should.equal(3); + + // Run upgrade + await exhaust(container); + + await asAlice.get('/v1/projects/1/forms/updateEntity.xml') + .then(({ text }) => { + text.should.equal(` + + + + + + + + + + + + + + + + + + +`); + }); + + await asAlice.get('/v1/projects/1/forms/offlineEntity.xml') + .then(({ text }) => text.should.equal(testData.forms.offlineEntity)); + })); + }); });