Skip to content

Commit

Permalink
More test and code refinement
Browse files Browse the repository at this point in the history
  • Loading branch information
ktuite committed Oct 10, 2024
1 parent 3206c26 commit b033b05
Show file tree
Hide file tree
Showing 3 changed files with 234 additions and 73 deletions.
3 changes: 3 additions & 0 deletions lib/model/frames/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ class Form extends Frame.define(

async withUpgradeEntityVersion() {
const xml = await updateEntityForm(this.xml, '2023.1.0', '2024.1.0', '_upgrade');
// If the XML doesnt change (not the version in question, or a parsing error), don't return the new partial Form
if (xml === this.xml)
return null;
const form = await Form.fromXml(xml);
return form.withAux('xls', { xlsBlobId: this.def.xlsBlobId });
}
Expand Down
6 changes: 4 additions & 2 deletions lib/worker/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,16 @@ const updateEntitiesVersion = async ({ Forms }, event) => {
const publishedVersion = await Forms.getByProjectAndXmlFormId(projectId, xmlFormId, true, Form.PublishedVersion).then(o => o.orNull());
if (publishedVersion && publishedVersion.currentDefId != null) {
const partial = await publishedVersion.withUpgradeEntityVersion();
await Forms.createVersion(partial, publishedVersion, true, true);
if (partial != null)
await Forms.createVersion(partial, publishedVersion, true, true);
}

const draftVersion = await Forms.getByProjectAndXmlFormId(projectId, xmlFormId, true, Form.DraftVersion).then(o => o.orNull());
if (draftVersion && draftVersion.draftDefId != null) {
const partial = await draftVersion.withUpgradeEntityVersion();
// update xml and version in place
await Forms._updateDef(draftVersion.def, { xml: partial.xml, version: partial.def.version });
if (partial != null)
await Forms._updateDef(draftVersion.def, { xml: partial.xml, version: partial.def.version });
}
};

Expand Down
298 changes: 227 additions & 71 deletions test/integration/other/form-entities-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,28 @@ const testData = require('../../data/xml');

const { exhaust } = require(appRoot + '/lib/worker/worker');

const upgradedUpdateEntity = `<?xml version="1.0"?>
<h:html xmlns="http://www.w3.org/2002/xforms" xmlns:h="http://www.w3.org/1999/xhtml" xmlns:jr="http://openrosa.org/javarosa" xmlns:entities="http://www.opendatakit.org/xforms">
<h:head>
<model entities:entities-version="2024.1.0">
<instance>
<data id="updateEntity" orx:version="1.0_upgrade">
<name/>
<age/>
<hometown/>
<meta>
<entity dataset="people" id="" update="" baseVersion="" trunkVersion="" branchId="">
<label/>
</entity>
</meta>
</data>
</instance>
<bind nodeset="/data/name" type="string" entities:saveto="first_name"/>
<bind nodeset="/data/age" type="int" entities:saveto="age"/>
</model>
</h:head>
</h:html>`;

describe('Update / migrate entities-version within form', () => {
describe('upgrading a 2023.1.0 update form', () => {
it('should upgrade a form with only a published version', testService(async (service, container) => {
Expand All @@ -31,29 +53,7 @@ describe('Update / migrate entities-version within form', () => {
});

await asAlice.get('/v1/projects/1/forms/updateEntity.xml')
.then(({ text }) => {
text.should.equal(`<?xml version="1.0"?>
<h:html xmlns="http://www.w3.org/2002/xforms" xmlns:h="http://www.w3.org/1999/xhtml" xmlns:jr="http://openrosa.org/javarosa" xmlns:entities="http://www.opendatakit.org/xforms">
<h:head>
<model entities:entities-version="2024.1.0">
<instance>
<data id="updateEntity" orx:version="1.0_upgrade">
<name/>
<age/>
<hometown/>
<meta>
<entity dataset="people" id="" update="" baseVersion="" trunkVersion="" branchId="">
<label/>
</entity>
</meta>
</data>
</instance>
<bind nodeset="/data/name" type="string" entities:saveto="first_name"/>
<bind nodeset="/data/age" type="int" entities:saveto="age"/>
</model>
</h:head>
</h:html>`);
});
.then(({ text }) => text.should.equal(upgradedUpdateEntity));
}));

it('should upgrade a form with only a draft version', testService(async (service, container) => {
Expand Down Expand Up @@ -86,29 +86,7 @@ describe('Update / migrate entities-version within form', () => {

// The XML is updated
await asAlice.get('/v1/projects/1/forms/updateEntity/draft.xml')
.then(({ text }) => {
text.should.equal(`<?xml version="1.0"?>
<h:html xmlns="http://www.w3.org/2002/xforms" xmlns:h="http://www.w3.org/1999/xhtml" xmlns:jr="http://openrosa.org/javarosa" xmlns:entities="http://www.opendatakit.org/xforms">
<h:head>
<model entities:entities-version="2024.1.0">
<instance>
<data id="updateEntity" orx:version="1.0_upgrade">
<name/>
<age/>
<hometown/>
<meta>
<entity dataset="people" id="" update="" baseVersion="" trunkVersion="" branchId="">
<label/>
</entity>
</meta>
</data>
</instance>
<bind nodeset="/data/name" type="string" entities:saveto="first_name"/>
<bind nodeset="/data/age" type="int" entities:saveto="age"/>
</model>
</h:head>
</h:html>`);
});
.then(({ text }) => text.should.equal(upgradedUpdateEntity));
}));

it('should upgrade a form with a draft version and a published version', testService(async (service, container) => {
Expand Down Expand Up @@ -143,38 +121,50 @@ describe('Update / migrate entities-version within form', () => {
body[1].version.should.equal('1.0');
});

const updatedXml = `<?xml version="1.0"?>
<h:html xmlns="http://www.w3.org/2002/xforms" xmlns:h="http://www.w3.org/1999/xhtml" xmlns:jr="http://openrosa.org/javarosa" xmlns:entities="http://www.opendatakit.org/xforms">
<h:head>
<model entities:entities-version="2024.1.0">
<instance>
<data id="updateEntity" orx:version="1.0_upgrade">
<name/>
<age/>
<hometown/>
<meta>
<entity dataset="people" id="" update="" baseVersion="" trunkVersion="" branchId="">
<label/>
</entity>
</meta>
</data>
</instance>
<bind nodeset="/data/name" type="string" entities:saveto="first_name"/>
<bind nodeset="/data/age" type="int" entities:saveto="age"/>
</model>
</h:head>
</h:html>`;

// The published form XML is updated
await asAlice.get('/v1/projects/1/forms/updateEntity.xml')
.then(({ text }) => text.should.equal(updatedXml));
.then(({ text }) => text.should.equal(upgradedUpdateEntity));

// The draft XML is updated
await asAlice.get('/v1/projects/1/forms/updateEntity/draft.xml')
.then(({ text }) => text.should.equal(updatedXml));
.then(({ text }) => text.should.equal(upgradedUpdateEntity));
}));

// only latest version of a form gets updated
it('should only upgrade the latest version of a form', testService(async (service, container) => {
const { Forms, Audits } = container;
const asAlice = await service.login('alice');

// Publish a form
await asAlice.post('/v1/projects/1/forms?publish=true')
.send(testData.forms.updateEntity)
.set('Content-Type', 'application/xml')
.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);

await asAlice.post('/v1/projects/1/forms/updateEntity/draft/publish');

const { acteeId } = await Forms.getByProjectAndXmlFormId(1, 'updateEntity').then(o => o.get());
await Audits.log(null, 'upgrade.process.form.entities_version', { acteeId });

// Run form upgrade
await exhaust(container);

await asAlice.get('/v1/projects/1/forms/updateEntity/versions')
.then(({ body }) => {
body.map(f => f.version).should.eql([ '3.0_upgrade', '3.0', '2.0', '1.0' ]);
});
}));

it('should carry forward the xlsx file of a published form', testService(async (service, container) => {
const { Forms, Audits } = container;
Expand Down Expand Up @@ -237,5 +227,171 @@ describe('Update / migrate entities-version within form', () => {
headers.etag.should.equal('"30fdb0e9115ea7ca6702573f521814d1"');
});
}));

it('should carry forward the attachments of a form', testService(async (service, container) => {
const { Forms, Audits } = container;
const asAlice = await service.login('alice');

const withAttachmentsEntities = testData.forms.withAttachments
.replace('<model>', '<model entities:entities-version="2023.1.0">')
.replace('</meta>', '<entity dataset="people" id="" update="" baseVersion=""><label/></entity></meta>');

// Upload a form and publish it
await asAlice.post('/v1/projects/1/forms')
.send(withAttachmentsEntities)
.set('Content-Type', 'application/xml')
.expect(200);

// Upload an attachment
await asAlice.post('/v1/projects/1/forms/withAttachments/draft/attachments/goodone.csv')
.send('test,csv\n1,2')
.set('Content-Type', 'text/csv');

// Publish the draft
await asAlice.post('/v1/projects/1/forms/withAttachments/draft/pubilsh');

// Create a draft
await asAlice.post('/v1/projects/1/forms/withAttachments/draft');

await asAlice.get('/v1/projects/1/forms/withAttachments/attachments')
.expect(200)
.then(({ body }) => {
// eslint-disable-next-line no-param-reassign
delete body[0].updatedAt;
body.should.eql([
{ name: 'goodone.csv', type: 'file', exists: true, blobExists: true, datasetExists: false },
{ name: 'goodtwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false }
]);
});

const { acteeId } = await Forms.getByProjectAndXmlFormId(1, 'withAttachments').then(o => o.get());
await Audits.log(null, 'upgrade.process.form.entities_version', { acteeId });

// Run form upgrade
await exhaust(container);

// Check form xml (published)
await asAlice.get('/v1/projects/1/forms/withAttachments.xml')
.then(({ text }) => {
text.includes('entities:entities-version="2024.1.0"').should.equal(true);
text.includes('version="_upgrade"').should.equal(true);
text.includes('trunkVersion="" branchId=""').should.equal(true);
});

// Check form xml (draft)
await asAlice.get('/v1/projects/1/forms/withAttachments/draft.xml')
.then(({ text }) => {
text.includes('entities:entities-version="2024.1.0"').should.equal(true);
text.includes('version="_upgrade"').should.equal(true);
text.includes('trunkVersion="" branchId=""').should.equal(true);
});

// Check attachments
await asAlice.get('/v1/projects/1/forms/withAttachments/attachments')
.expect(200)
.then(({ body }) => {
// eslint-disable-next-line no-param-reassign
delete body[0].updatedAt;
body.should.eql([
{ name: 'goodone.csv', type: 'file', exists: true, blobExists: true, datasetExists: false },
{ name: 'goodtwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false }
]);
});

await asAlice.get('/v1/projects/1/forms/withAttachments/draft/attachments')
.expect(200)
.then(({ body }) => {
// eslint-disable-next-line no-param-reassign
delete body[0].updatedAt;
body.should.eql([
{ name: 'goodone.csv', type: 'file', exists: true, blobExists: true, datasetExists: false },
{ name: 'goodtwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false }
]);
});
}));
});

describe('audit logging and errors', () => {
it('should update the audit log event for a successful upgrade', testService(async (service, container) => {
const { Forms, Audits } = container;
const asAlice = await service.login('alice');

// Publish a form
await asAlice.post('/v1/projects/1/forms?publish=true')
.send(testData.forms.updateEntity)
.set('Content-Type', 'application/xml')
.expect(200);

const { acteeId } = await Forms.getByProjectAndXmlFormId(1, 'updateEntity').then(o => o.get());
await Audits.log(null, 'upgrade.process.form.entities_version', { acteeId });

// Run form upgrade
await exhaust(container);

const audit = await Audits.getLatestByAction('upgrade.process.form.entities_version').then((o) => o.get());
audit.processed.should.be.a.recentDate();
audit.failures.should.equal(0);
}));

it('should not update form if there is a problem, but should process audit event', testService(async (service, container) => {
const { Forms, Audits } = container;
const asAlice = await service.login('alice');

// Publish an invalid XML form
const invalidForm = testData.forms.updateEntity.replace('<entity', '<something');
await asAlice.post('/v1/projects/1/forms?publish=true')
.send(invalidForm)
.set('Content-Type', 'application/xml')
.expect(200);

await asAlice.post('/v1/projects/1/forms/updateEntity/draft');

const { acteeId } = await Forms.getByProjectAndXmlFormId(1, 'updateEntity').then(o => o.get());
await Audits.log(null, 'upgrade.process.form.entities_version', { acteeId });

// Run form upgrade
await exhaust(container);

// The published form XML is the same
await asAlice.get('/v1/projects/1/forms/updateEntity.xml')
.then(({ text }) => text.should.equal(invalidForm));

// The draft XML is the same
await asAlice.get('/v1/projects/1/forms/updateEntity/draft.xml')
.then(({ text }) => text.should.equal(invalidForm));

// Check form versions
await asAlice.get('/v1/projects/1/forms/updateEntity/versions')
.then(({ body }) => {
body.map(f => f.version).should.eql([ '1.0' ]);
});

// Check audit log
const audit = await Audits.getLatestByAction('upgrade.process.form.entities_version').then((o) => o.get());
audit.processed.should.be.a.recentDate();
audit.failures.should.equal(0);
}));

it('should not update form if the entities version is not the target one (e.g. 2024.1)', testService(async (service, container) => {
const { Forms, Audits } = container;
const asAlice = await service.login('alice');

// Publish an XML form of the right version
const invalidForm = testData.forms.offlineEntity;
await asAlice.post('/v1/projects/1/forms?publish=true')
.send(invalidForm)
.set('Content-Type', 'application/xml')
.expect(200);

const { acteeId } = await Forms.getByProjectAndXmlFormId(1, 'offlineEntity').then(o => o.get());
await Audits.log(null, 'upgrade.process.form.entities_version', { acteeId });

// Run form upgrade
await exhaust(container);

// The published form XML is the same
await asAlice.get('/v1/projects/1/forms/offlineEntity.xml')
.then(({ text }) => text.should.equal(testData.forms.offlineEntity));
}));
});
});

0 comments on commit b033b05

Please sign in to comment.