Skip to content

Commit ad061f6

Browse files
authored
Check for meta group on form upload (getodk#759)
* Check for meta in form xml and update tests * Only check for missingMeta on new forms
1 parent 5d6ff83 commit ad061f6

File tree

10 files changed

+166
-48
lines changed

10 files changed

+166
-48
lines changed

lib/model/query/forms.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ const createNew = (partial, project, publish = false) => async ({ run, Datasets,
7575
(partial.aux.key.isDefined() ? resolve(Option.none()) : getDataset(partial.xml)) // Don't parse dataset schema if Form has encryption key
7676
]);
7777

78+
// Check that meta field (group containing instanceId and name) exists
79+
await Forms.checkMeta(fields);
80+
7881
// Check for xmlFormId collisions with previously deleted forms
7982
await Forms.checkDeletedForms(partial.xmlFormId, project.id);
8083
await Forms.rejectIfWarnings();
@@ -617,6 +620,13 @@ order by actors."displayName" asc`)
617620
////////////////////////////////////////////////////////////////////////////////
618621
// CHECKING CONSTRAINTS, STRUCTURAL CHANGES, ETC.
619622

623+
// This will check if a form contains a meta group (for capturing instanceID).
624+
// It is only to be used for newly uploaded forms.
625+
const checkMeta = (fields) => () =>
626+
(fields.some((f) => (f.name === 'meta' && f.type === 'structure'))
627+
? resolve()
628+
: reject(Problem.user.missingMeta()));
629+
620630
const checkDeletedForms = (xmlFormId, projectId) => ({ maybeOne, context }) => (context.query.ignoreWarnings ? resolve() : maybeOne(sql`SELECT 1 FROM forms WHERE "xmlFormId" = ${xmlFormId} AND "projectId" = ${projectId} AND "deletedAt" IS NOT NULL LIMIT 1`)
621631
.then(deletedForm => {
622632
if (deletedForm.isDefined()) {
@@ -681,7 +691,7 @@ module.exports = {
681691
getByProjectId, getByProjectAndXmlFormId, getByProjectAndNumericId,
682692
getAllByAuth,
683693
getFields, getBinaryFields, getStructuralFields, getMergedFields,
684-
rejectIfWarnings, checkDeletedForms, checkStructuralChange, checkFieldDowncast,
694+
rejectIfWarnings, checkMeta, checkDeletedForms, checkStructuralChange, checkFieldDowncast,
685695
_newSchema,
686696
lockDefs, getAllSubmitters
687697
};

lib/util/problem.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ const problems = {
108108
// problem parsing the entity form
109109
datasetLinkNotAllowed: problem(400.26, () => 'Dataset can only be linked to attachments with "Data File" type.'),
110110

111+
// meta group in form definition is missing
112+
missingMeta: problem(400.27, () => 'The form does not contain a \'meta\' group.'),
113+
111114
// no detail information for security reasons.
112115
authenticationFailed: problem(401.2, () => 'Could not authenticate with the provided credentials.'),
113116

test/data/xml.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,15 @@ module.exports = {
173173
<model>
174174
<instance>
175175
<data id="withAttachments">
176+
<meta>
177+
<instanceID/>
178+
</meta>
176179
<name/>
177180
<age/>
178181
<hometown/>
179182
</data>
180183
</instance>
184+
<bind nodeset="/data/meta/instanceID" type="string" readonly="true()" calculate="concat('uuid:', uuid())"/>
181185
<instance id="mydata" src="badnoroot.xls"/>
182186
<instance id="seconddata" src="jr://files/badsubpath.csv"/>
183187
<instance id="thirddata" src="jr://file/goodone.csv"/>
@@ -202,10 +206,14 @@ module.exports = {
202206
<model>
203207
<instance>
204208
<data id="itemsets">
209+
<meta>
210+
<instanceID/>
211+
</meta>
205212
<name/>
206213
</data>
207214
</instance>
208215
<instance id="itemsets" src="jr://file/itemsets.csv"/>
216+
<bind nodeset="/data/meta/instanceID" type="string" readonly="true()" calculate="concat('uuid:', uuid())"/>
209217
<bind nodeset="/data/name" type="string"/>
210218
</model>
211219
</h:head>
@@ -316,10 +324,14 @@ module.exports = {
316324
<model>
317325
<instance>
318326
<data id="selectMultiple">
327+
<meta>
328+
<instanceID/>
329+
</meta>
319330
<q1/>
320331
<g1><q2/></g1>
321332
</data>
322333
</instance>
334+
<bind nodeset="/data/meta/instanceID" type="string" readonly="true()" calculate="concat('uuid:', uuid())"/>
323335
<bind nodeset="/data/q1" type="string"/>
324336
<bind nodeset="/data/g1/q2" type="string"/>
325337
</model>

test/integration/api/forms/draft.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,35 @@ describe('api: /projects/:id/forms (drafts)', () => {
406406
.set('Content-Type', 'application/xml')
407407
.expect(200)))));
408408

409+
410+
it('should allow new draft with missing meta group', testService(async (service) => {
411+
// This case is not expected, but it mimics a different scenario where there are
412+
// already meta-less forms in a user's central repo and they need to be able to update them
413+
// without breaking their workflows.
414+
const asAlice = await service.login('alice');
415+
416+
const simpleMissingMeta = `<h:html xmlns="http://www.w3.org/2002/xforms" xmlns:h="http://www.w3.org/1999/xhtml">
417+
<h:head>
418+
<h:title>Simple</h:title>
419+
<model>
420+
<instance>
421+
<data id="simple">
422+
<name/>
423+
<age/>
424+
</data>
425+
</instance>
426+
<bind nodeset="/data/name" type="string"/>
427+
<bind nodeset="/data/age" type="int"/>
428+
</model>
429+
</h:head>
430+
</h:html>`;
431+
432+
await asAlice.post('/v1/projects/1/forms/simple/draft?ignoreWarnings=true')
433+
.send(simpleMissingMeta)
434+
.set('Content-Type', 'application/xml')
435+
.expect(200);
436+
}));
437+
409438
it('should identify attachments', testService((service) =>
410439
service.login('alice', (asAlice) =>
411440
asAlice.post('/v1/projects/1/forms/simple/draft?ignoreWarnings=true')

test/integration/api/forms/forms.js

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,59 @@ describe('api: /projects/:id/forms (create, read, update)', () => {
438438
});
439439
}));
440440

441+
it('should reject form with missing meta group', testService(async (service) => {
442+
const asAlice = await service.login('alice');
443+
444+
const missingMeta = `<h:html xmlns="http://www.w3.org/2002/xforms" xmlns:h="http://www.w3.org/1999/xhtml">
445+
<h:head>
446+
<h:title>Missing Meta</h:title>
447+
<model>
448+
<instance>
449+
<data id="missingMeta">
450+
<name/>
451+
<age/>
452+
</data>
453+
</instance>
454+
<bind nodeset="/data/name" type="string"/>
455+
<bind nodeset="/data/age" type="int"/>
456+
</model>
457+
</h:head>
458+
459+
</h:html>`;
460+
461+
await asAlice.post('/v1/projects/1/forms')
462+
.send(missingMeta)
463+
.set('Content-Type', 'application/xml')
464+
.expect(400);
465+
}));
466+
467+
it('should reject form with meta field that is not a group', testService(async (service) => {
468+
const asAlice = await service.login('alice');
469+
470+
const missingMeta = `<h:html xmlns="http://www.w3.org/2002/xforms" xmlns:h="http://www.w3.org/1999/xhtml">
471+
<h:head>
472+
<h:title>Non Group Meta</h:title>
473+
<model>
474+
<instance>
475+
<data id="missingMeta">
476+
<meta/>
477+
<name/>
478+
<age/>
479+
</data>
480+
</instance>
481+
<bind nodeset="/data/name" type="string"/>
482+
<bind nodeset="/data/age" type="int"/>
483+
</model>
484+
</h:head>
485+
486+
</h:html>`;
487+
488+
await asAlice.post('/v1/projects/1/forms')
489+
.send(missingMeta)
490+
.set('Content-Type', 'application/xml')
491+
.expect(400);
492+
}));
493+
441494
it('should create the form for xml files with warnings given ignoreWarnings', testService(async (service) => {
442495
const asAlice = await service.login('alice', identity);
443496

@@ -809,6 +862,8 @@ describe('api: /projects/:id/forms (create, read, update)', () => {
809862
.expect(200)
810863
.then(({ body }) => {
811864
body.should.eql([
865+
{ name: 'meta', path: '/meta', type: 'structure', binary: null, selectMultiple: null },
866+
{ name: 'instanceID', path: '/meta/instanceID', type: 'string', binary: null, selectMultiple: null },
812867
{ name: 'q1', path: '/q1', type: 'string', binary: null, selectMultiple: true },
813868
{ name: 'g1', path: '/g1', type: 'structure', binary: null, selectMultiple: null },
814869
{ name: 'q2', path: '/g1/q2', type: 'string', binary: null, selectMultiple: true }
@@ -821,13 +876,16 @@ describe('api: /projects/:id/forms (create, read, update)', () => {
821876
<model>
822877
<instance>
823878
<data id="sanitize">
879+
<meta>
880+
<instanceID>
881+
</meta>
824882
<q1.8>
825883
<17/>
826884
</q1.8>
827885
<4.2/>
828886
</data>
829887
</instance>
830-
888+
<bind nodeset="/data/meta/instanceID" type="string" readonly="true()" calculate="concat('uuid:', uuid())"/>
831889
<bind nodeset="/data/q1.8/17" type="string" readonly="true()" calculate="concat('uuid:', uuid())"/>
832890
<bind nodeset="/data/4.2" type="number"/>
833891
</model>
@@ -850,6 +908,8 @@ describe('api: /projects/:id/forms (create, read, update)', () => {
850908
.expect(200)
851909
.then(({ body }) => {
852910
body.should.eql([
911+
{ name: 'meta', path: '/meta', type: 'structure', binary: null, selectMultiple: null },
912+
{ name: 'instanceID', path: '/meta/instanceID', type: 'string', binary: null, selectMultiple: null },
853913
{ name: 'q1_8', path: '/q1_8', type: 'structure', binary: null, selectMultiple: null },
854914
{ name: '_17', path: '/q1_8/_17', type: 'string', binary: null, selectMultiple: null },
855915
{ name: '_4_2', path: '/_4_2', type: 'number', binary: null, selectMultiple: null }

test/integration/api/forms/list.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ describe('api: /projects/:id/forms (listing forms)', () => {
328328
<formID>withAttachments</formID>
329329
<name>withAttachments</name>
330330
<version></version>
331-
<hash>md5:7eb21b5b123b0badcf2b8f50bcf1cbd0</hash>
331+
<hash>md5:dda89055c8fec222458f702aead30a83</hash>
332332
<downloadUrl>${domain}/v1/projects/1/forms/withAttachments.xml</downloadUrl>
333333
<manifestUrl>${domain}/v1/projects/1/forms/withAttachments/manifest</manifestUrl>
334334
</xform>

test/integration/api/forms/test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ describe('api: /projects/:id/forms (testing drafts)', () => {
120120
<formID>withAttachments</formID>
121121
<name>withAttachments</name>
122122
<version></version>
123-
<hash>md5:7eb21b5b123b0badcf2b8f50bcf1cbd0</hash>
123+
<hash>md5:dda89055c8fec222458f702aead30a83</hash>
124124
<downloadUrl>${domain}/v1/test/${token}/projects/1/forms/withAttachments/draft.xml</downloadUrl>
125125
<manifestUrl>${domain}/v1/test/${token}/projects/1/forms/withAttachments/draft/manifest</manifestUrl>
126126
</xform>

test/integration/api/submissions.js

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1575,11 +1575,11 @@ describe('api: /forms/:id/submissions', () => {
15751575
.then((result) => {
15761576
result.filenames.should.containDeep([ 'selectMultiple.csv' ]);
15771577
const lines = result['selectMultiple.csv'].split('\n');
1578-
lines[0].should.equal('SubmissionDate,q1,q1/a,q1/b,g1-q2,g1-q2/m,g1-q2/x,g1-q2/y,g1-q2/z,KEY,SubmitterID,SubmitterName,AttachmentsPresent,AttachmentsExpected,Status,ReviewState,DeviceID,Edits,FormVersion');
1578+
lines[0].should.equal('SubmissionDate,meta-instanceID,q1,q1/a,q1/b,g1-q2,g1-q2/m,g1-q2/x,g1-q2/y,g1-q2/z,KEY,SubmitterID,SubmitterName,AttachmentsPresent,AttachmentsExpected,Status,ReviewState,DeviceID,Edits,FormVersion');
15791579
lines[1].slice('yyyy-mm-ddThh:mm:ss._msZ'.length)
1580-
.should.equal(',b,0,1,m x,1,1,0,0,two,5,Alice,0,0,,,,0,');
1580+
.should.equal(',two,b,0,1,m x,1,1,0,0,two,5,Alice,0,0,,,,0,');
15811581
lines[2].slice('yyyy-mm-ddThh:mm:ss._msZ'.length)
1582-
.should.equal(',a b,1,1,x y z,0,1,1,1,one,5,Alice,0,0,,,,0,');
1582+
.should.equal(',one,a b,1,1,x y z,0,1,1,1,one,5,Alice,0,0,,,,0,');
15831583
})))));
15841584

15851585
it('should omit multiples it does not know about', testService((service) =>
@@ -1597,11 +1597,11 @@ describe('api: /forms/:id/submissions', () => {
15971597
.then((result) => {
15981598
result.filenames.should.containDeep([ 'selectMultiple.csv' ]);
15991599
const lines = result['selectMultiple.csv'].split('\n');
1600-
lines[0].should.equal('SubmissionDate,q1,g1-q2,KEY,SubmitterID,SubmitterName,AttachmentsPresent,AttachmentsExpected,Status,ReviewState,DeviceID,Edits,FormVersion');
1600+
lines[0].should.equal('SubmissionDate,meta-instanceID,q1,g1-q2,KEY,SubmitterID,SubmitterName,AttachmentsPresent,AttachmentsExpected,Status,ReviewState,DeviceID,Edits,FormVersion');
16011601
lines[1].slice('yyyy-mm-ddThh:mm:ss._msZ'.length)
1602-
.should.equal(',b,m x,two,5,Alice,0,0,,,,0,');
1602+
.should.equal(',two,b,m x,two,5,Alice,0,0,,,,0,');
16031603
lines[2].slice('yyyy-mm-ddThh:mm:ss._msZ'.length)
1604-
.should.equal(',a b,x y z,one,5,Alice,0,0,,,,0,');
1604+
.should.equal(',one,a b,x y z,one,5,Alice,0,0,,,,0,');
16051605
})))));
16061606

16071607
it('should split select multiples and filter given both options', testService((service, container) =>
@@ -1741,11 +1741,11 @@ describe('api: /forms/:id/submissions', () => {
17411741
.then((result) => {
17421742
result.filenames.should.containDeep([ 'selectMultiple.csv' ]);
17431743
const lines = result['selectMultiple.csv'].split('\n');
1744-
lines[0].should.equal('SubmissionDate,q1,q1/a,q1/b,q2,q2/m,q2/x,q2/y,q2/z,KEY,SubmitterID,SubmitterName,AttachmentsPresent,AttachmentsExpected,Status,ReviewState,DeviceID,Edits,FormVersion');
1744+
lines[0].should.equal('SubmissionDate,instanceID,q1,q1/a,q1/b,q2,q2/m,q2/x,q2/y,q2/z,KEY,SubmitterID,SubmitterName,AttachmentsPresent,AttachmentsExpected,Status,ReviewState,DeviceID,Edits,FormVersion');
17451745
lines[1].slice('yyyy-mm-ddThh:mm:ss._msZ'.length)
1746-
.should.equal(',b,0,1,m x,1,1,0,0,two,5,Alice,0,0,,,,0,');
1746+
.should.equal(',two,b,0,1,m x,1,1,0,0,two,5,Alice,0,0,,,,0,');
17471747
lines[2].slice('yyyy-mm-ddThh:mm:ss._msZ'.length)
1748-
.should.equal(',a b,1,1,x y z,0,1,1,1,one,5,Alice,0,0,,,,0,');
1748+
.should.equal(',one,a b,1,1,x y z,0,1,1,1,one,5,Alice,0,0,,,,0,');
17491749
})))));
17501750

17511751
it('should properly count present attachments', testService((service) =>
@@ -2186,11 +2186,11 @@ one,h,/data/h,2000-01-01T00:06,2000-01-01T00:07,-5,-6,,ee,ff
21862186
// eslint-disable-next-line no-multi-spaces
21872187
.then(({ text }) => {
21882188
const lines = text.split('\n');
2189-
lines[0].should.equal('SubmissionDate,q1,q1/a,q1/b,g1-q2,g1-q2/m,g1-q2/x,g1-q2/y,g1-q2/z,KEY,SubmitterID,SubmitterName,AttachmentsPresent,AttachmentsExpected,Status,ReviewState,DeviceID,Edits,FormVersion');
2189+
lines[0].should.equal('SubmissionDate,meta-instanceID,q1,q1/a,q1/b,g1-q2,g1-q2/m,g1-q2/x,g1-q2/y,g1-q2/z,KEY,SubmitterID,SubmitterName,AttachmentsPresent,AttachmentsExpected,Status,ReviewState,DeviceID,Edits,FormVersion');
21902190
lines[1].slice('yyyy-mm-ddThh:mm:ss._msZ'.length)
2191-
.should.equal(',b,0,1,m x,1,1,0,0,two,5,Alice,0,0,,,,0,');
2191+
.should.equal(',two,b,0,1,m x,1,1,0,0,two,5,Alice,0,0,,,,0,');
21922192
lines[2].slice('yyyy-mm-ddThh:mm:ss._msZ'.length)
2193-
.should.equal(',a b,1,1,x y z,0,1,1,1,one,5,Alice,0,0,,,,0,');
2193+
.should.equal(',one,a b,1,1,x y z,0,1,1,1,one,5,Alice,0,0,,,,0,');
21942194
})))));
21952195

21962196
it('should omit group paths if ?groupPaths=false is given', testService((service) =>
@@ -2279,11 +2279,13 @@ one,h,/data/h,2000-01-01T00:06,2000-01-01T00:07,-5,-6,,ee,ff
22792279
<model>
22802280
<instance>
22812281
<data id="selectMultiple" version='3'>
2282+
<meta><instanceID/></meta>
22822283
<q1/>
22832284
<g1><q2/></g1>
22842285
<q3/>
22852286
</data>
22862287
</instance>
2288+
<bind nodeset="/data/meta/instanceID" type="string"/>
22872289
<bind nodeset="/data/q1" type="string"/>
22882290
<bind nodeset="/data/g1/q2" type="string"/>
22892291
<bind nodeset="/data/q3" type="string"/>
@@ -2323,13 +2325,13 @@ one,h,/data/h,2000-01-01T00:06,2000-01-01T00:07,-5,-6,,ee,ff
23232325
.expect(200)
23242326
.then(({ text }) => {
23252327
const lines = text.split('\n');
2326-
lines[0].should.equal('SubmissionDate,q1,q1/a,q1/b,g1-q2,g1-q2/m,g1-q2/x,g1-q2/y,g1-q2/z,q3,q3/z,KEY,SubmitterID,SubmitterName,AttachmentsPresent,AttachmentsExpected,Status,ReviewState,DeviceID,Edits,FormVersion');
2328+
lines[0].should.equal('SubmissionDate,meta-instanceID,q1,q1/a,q1/b,g1-q2,g1-q2/m,g1-q2/x,g1-q2/y,g1-q2/z,q3,q3/z,KEY,SubmitterID,SubmitterName,AttachmentsPresent,AttachmentsExpected,Status,ReviewState,DeviceID,Edits,FormVersion');
23272329
lines[1].slice('yyyy-mm-ddThh:mm:ss._msZ'.length)
2328-
.should.equal(',a b,1,1,m,1,0,0,0,z,1,three,5,Alice,0,0,,,,0,3');
2330+
.should.equal(',three,a b,1,1,m,1,0,0,0,z,1,three,5,Alice,0,0,,,,0,3');
23292331
lines[2].slice('yyyy-mm-ddThh:mm:ss._msZ'.length)
2330-
.should.equal(',b,0,1,m x,1,1,0,0,,0,two,5,Alice,0,0,,,,0,');
2332+
.should.equal(',two,b,0,1,m x,1,1,0,0,,0,two,5,Alice,0,0,,,,0,');
23312333
lines[3].slice('yyyy-mm-ddThh:mm:ss._msZ'.length)
2332-
.should.equal(',a b,1,1,x y z,0,1,1,1,,0,one,5,Alice,0,0,,,,0,');
2334+
.should.equal(',one,a b,1,1,x y z,0,1,1,1,,0,one,5,Alice,0,0,,,,0,');
23332335
})))));
23342336
});
23352337
});
@@ -2471,11 +2473,11 @@ one,h,/data/h,2000-01-01T00:06,2000-01-01T00:07,-5,-6,,ee,ff
24712473
.then((result) => {
24722474
result.filenames.should.containDeep([ 'selectMultiple.csv' ]);
24732475
const lines = result['selectMultiple.csv'].split('\n');
2474-
lines[0].should.equal('SubmissionDate,q1,q1/a,q1/b,g1-q2,g1-q2/m,g1-q2/x,g1-q2/y,g1-q2/z,KEY,SubmitterID,SubmitterName,AttachmentsPresent,AttachmentsExpected,Status,ReviewState,DeviceID,Edits,FormVersion');
2476+
lines[0].should.equal('SubmissionDate,meta-instanceID,q1,q1/a,q1/b,g1-q2,g1-q2/m,g1-q2/x,g1-q2/y,g1-q2/z,KEY,SubmitterID,SubmitterName,AttachmentsPresent,AttachmentsExpected,Status,ReviewState,DeviceID,Edits,FormVersion');
24752477
lines[1].slice('yyyy-mm-ddThh:mm:ss._msZ'.length)
2476-
.should.equal(',b,0,1,m x,1,1,0,0,two,,,0,0,,,,0,');
2478+
.should.equal(',two,b,0,1,m x,1,1,0,0,two,,,0,0,,,,0,');
24772479
lines[2].slice('yyyy-mm-ddThh:mm:ss._msZ'.length)
2478-
.should.equal(',a b,1,1,x y z,0,1,1,1,one,,,0,0,,,,0,');
2480+
.should.equal(',one,a b,1,1,x y z,0,1,1,1,one,,,0,0,,,,0,');
24792481
})))));
24802482
});
24812483

test/integration/other/select-many.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,12 @@ describe('select many value processing', () => {
9494
<model>
9595
<instance>
9696
<data id="selectMultiple">
97+
<meta><instanceID/></meta>
9798
<q1/>
9899
<g1><q2/></g1>
99100
</data>
100101
</instance>
102+
<bind nodeset="/data/meta/instanceID" type="string"/>
101103
<bind nodeset="/data/q1" type="string"/>
102104
<bind nodeset="/data/g1/q2" type="string"/>
103105
</model>
@@ -143,11 +145,11 @@ describe('select many value processing', () => {
143145
.expect(200)
144146
.then(({ text }) => {
145147
const lines = text.split('\n');
146-
lines[0].should.equal('SubmissionDate,q1,q1/a,q1/b,g1-q2,g1-q2/x,g1-q2/y,g1-q2/z,KEY,SubmitterID,SubmitterName,AttachmentsPresent,AttachmentsExpected,Status,ReviewState,DeviceID,Edits,FormVersion');
148+
lines[0].should.equal('SubmissionDate,meta-instanceID,q1,q1/a,q1/b,g1-q2,g1-q2/x,g1-q2/y,g1-q2/z,KEY,SubmitterID,SubmitterName,AttachmentsPresent,AttachmentsExpected,Status,ReviewState,DeviceID,Edits,FormVersion');
147149
lines[1].slice('yyyy-mm-ddThh:mm:ss._msZ'.length)
148-
.should.equal(',a b,1,1,x y z,1,1,1,one,5,Alice,0,0,,,,0,2');
150+
.should.equal(',one,a b,1,1,x y z,1,1,1,one,5,Alice,0,0,,,,0,2');
149151
lines[2].slice('yyyy-mm-ddThh:mm:ss._msZ'.length)
150-
.should.equal(',b,0,1,x,1,0,0,two,5,Alice,0,0,,,,0,');
152+
.should.equal(',two,b,0,1,x,1,0,0,two,5,Alice,0,0,,,,0,');
151153
});
152154
}));
153155
});

0 commit comments

Comments
 (0)