Skip to content

Commit 5f53ab9

Browse files
committed
test(cmc): contract test for readOffer wire-shape (streams not streamIds)
Stubs pryv.Connection so the cmc.readOffer call can be unit-tested end-to-end against an asserted apiOne wire-shape. Catches the class of bug where an events.get caller uses streamIds (the events.create write-target field) instead of streams (the recursive read filter) — the api-server schema rejects the wrong field with OBJECT_ADDITIONAL_PROPERTIES, but the previous unit suite only mocked Connection.apiOne and never asserted on the params shape.
1 parent 3b2e6f4 commit 5f53ab9

1 file changed

Lines changed: 50 additions & 0 deletions

File tree

components/pryv-cmc/test/cmc.test.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,56 @@ describe('[CMCL1] @pryv/cmc Level-1 protocol functions', function () {
293293
});
294294
});
295295

296+
describe('[CMCL1O] readOffer', function () {
297+
/**
298+
* Build a fake `pryv` module that returns a connection-like stub
299+
* for `new pryv.Connection(url)`. The stub records every apiOne
300+
* call so we can assert its wire-shape (Pryv's events.get takes
301+
* `streams` — NOT `streamIds`, which is the write target on
302+
* events.create. The api-server schema rejects the wrong field with
303+
* `OBJECT_ADDITIONAL_PROPERTIES`).
304+
*
305+
* Bug history: readOffer used to call events.get with
306+
* `streamIds: [...]`, which the api-server rejected at the schema
307+
* layer — every `cmc.readOffer(url)` threw on the first await.
308+
* Existing unit tests passed only because they exercised
309+
* `acceptInvite` (which catches readOffer errors silently) and
310+
* never asserted on the wire-shape of the inner call.
311+
*/
312+
function fakePryvWithApiOne (apiOneFn) {
313+
const stub = {
314+
apiOne: apiOneFn,
315+
service: { info: async () => { throw new Error('not stubbed'); } },
316+
accessInfo: async () => { throw new Error('not stubbed'); }
317+
};
318+
return {
319+
Connection: function () { return stub; },
320+
utils: { decomposeAPIEndpoint: () => ({ username: null, host: '' }) },
321+
_stub: stub
322+
};
323+
}
324+
325+
it('[CMCL1OA] calls events.get with `streams` (not `streamIds`) — schema-rejected otherwise', async function () {
326+
const calls = [];
327+
const fakePryv = fakePryvWithApiOne(async function (method, params, expectedKey) {
328+
calls.push({ method, params, expectedKey });
329+
if (method === 'events.get') {
330+
return { events: [{ id: 'offer-1', content: { request: { permissions: [], consent: { en: 'ok' } }, requesterMeta: { displayName: 'X' } } }] }[expectedKey];
331+
}
332+
throw new Error('unexpected method: ' + method);
333+
});
334+
await cmc.readOffer('https://Tok@example.com/', { pryv: fakePryv });
335+
expect(calls).to.have.length.greaterThanOrEqual(1);
336+
const getCall = calls.find(function (c) { return c.method === 'events.get'; });
337+
expect(getCall, 'expected one events.get call').to.exist;
338+
// The contract: `streams` is the read filter (recursive). `streamIds`
339+
// here is the api-server-rejected typo.
340+
expect(getCall.params).to.have.property('streams');
341+
expect(getCall.params.streams).to.deep.equal([':_cmc:_internal:offer']);
342+
expect(getCall.params).to.not.have.property('streamIds');
343+
});
344+
});
345+
296346
describe('[CMCL1G] getInviteStatus', function () {
297347
it('[CMCL1GA] reads events.getOne by id and returns InviteRecord', async function () {
298348
const conn = makeStubConnection({

0 commit comments

Comments
 (0)