Skip to content

Commit

Permalink
singleRowToOData(): handle unmatched repeatId (#1216)
Browse files Browse the repository at this point in the history
Throw specific error when `$skiptoken.repeatId` does not match a real record.

Follow-up to #1115
  • Loading branch information
alxndrsn authored Nov 7, 2024
1 parent 7005a40 commit a0cb196
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/formats/odata.js
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ const singleRowToOData = (fields, row, domain, originalUrl, query) => {

if (skipToken) {
offset = filtered.findIndex(s => skipToken.repeatId === s.__id) + 1;
if (offset === 0) throw Problem.user.odataRepeatIdNotFound();
}

const pared = filtered.slice(offset, offset + limit);
Expand Down
2 changes: 2 additions & 0 deletions lib/util/problem.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ const problems = {
// { expected: "list of expected properties", actual: "list of provided properties" }
unexpectedProperties: problem(400.33, ({ expected, actual }) => `Expected properties: (${expected.join(', ')}). Got (${actual.join(', ')}).`),

odataRepeatIdNotFound: problem(400.34, () => 'Record associated with the provided $skiptoken not found.'),

// no detail information for security reasons.
authenticationFailed: problem(401.2, () => 'Could not authenticate with the provided credentials.'),

Expand Down
32 changes: 32 additions & 0 deletions test/unit/formats/odata.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { fieldsFor, MockField } = require(appRoot + '/test/util/schema');
const testData = require(appRoot + '/test/data/xml');
const should = require('should');
const { QueryOptions } = require('../../../lib/util/db');
const Problem = require(appRoot + '/lib/util/problem');

// Helpers to deal with repeated system metadata generation.
const submitter = { id: 5, displayName: 'Alice' };
Expand Down Expand Up @@ -940,6 +941,37 @@ describe('odata message composition', () => {
const billy = { __id: 'cf9a1b5cc83c6d6270c1eb98860d294eac5d526d', age: 4, name: 'Billy' };
const blain = { __id: 'c76d0ccc6d5da236be7b93b985a80413d2e3e172', age: 6, name: 'Blaine' };

const nomatch = '0000000000000000000000000000000000000000';

[
{
$top: 0,
skiptoken: { repeatId: nomatch },
},
{
$top: 1,
skiptoken: { repeatId: nomatch },
},
{
$top: 2,
skiptoken: { repeatId: nomatch },
},
{
$top: undefined,
skiptoken: { repeatId: nomatch },
},
].forEach(({ $top, skiptoken }) =>
it(`should throw error for ${[$top, JSON.stringify(skiptoken)]}`, () =>
fieldsFor(testData.forms.withrepeat)
.then((fields) => {
const submission = mockSubmission('two', testData.instances.withrepeat.two);
const $skiptoken = '01' + Buffer.from(JSON.stringify(skiptoken)).toString('base64');
const query = { $top, $skiptoken };
const originaUrl = "/withrepeat.svc/Submissions('two')/children/child"; // doesn't have to include query string
return singleRowToOData(fields, submission, 'http://localhost:8989', originaUrl, query);
})
.should.be.rejectedWith(Problem, { problemCode: 400.34, message: 'Record associated with the provided $skiptoken not found.' })));

[
{
$top: 0,
Expand Down

0 comments on commit a0cb196

Please sign in to comment.