test(plugins): cover the mock double and the child proxy for addEntryPhoto

The handler had eight cases, its two doubles had none, which Sonar counted as
new code without coverage. The mock is the more useful of the two to pin
anyway: an importer developing against it should hit the same refusals it will
hit in production, so the SVG, the missing extension, the empty payload and the
size cap are all asserted against the double rather than only against the host.
This commit is contained in:
Maurice
2026-08-28 17:39:38 +02:00
committed by Maurice
parent 661777f81f
commit 6c7e257c17
2 changed files with 35 additions and 0 deletions
+32
View File
@@ -288,6 +288,38 @@ describe('createMockHost', () => {
await expect(ungranted.ctx.journal.createJourney({ title: 'x' })).rejects.toThrow(/PERMISSION_DENIED/);
});
it('journal.addEntryPhoto refuses in the mock what the host refuses in production', async () => {
const png = Buffer.from('89504e470d0a1a0a', 'hex').toString('base64');
const host = createMockHost({
grants: ['db:write:journal'],
actingUserId: 42,
journals: [{ id: 1, title: 'Japan' }],
journalEntries: [{ id: 5, journey_id: 1, entry_date: '2027-04-01' }],
});
const photo = await host.ctx.journal.addEntryPhoto(5, { name: 'tokyo.jpg', content_base64: png, caption: 'Arrival' });
expect(photo).toMatchObject({ entry_id: 5, caption: 'Arrival' });
// An importer that hits one of these in the mock would hit it in production too,
// which is the whole point of the double refusing the same things.
await expect(host.ctx.journal.addEntryPhoto(999, { name: 'a.jpg', content_base64: png }))
.rejects.toThrow(/RESOURCE_FORBIDDEN/);
await expect(host.ctx.journal.addEntryPhoto(5, { name: 'map.svg', content_base64: png }))
.rejects.toThrow(/not an allowed image type/);
await expect(host.ctx.journal.addEntryPhoto(5, { name: 'noext', content_base64: png }))
.rejects.toThrow(/not an allowed image type/);
await expect(host.ctx.journal.addEntryPhoto(5, { name: '', content_base64: png }))
.rejects.toThrow(/name is required/);
await expect(host.ctx.journal.addEntryPhoto(5, { name: 'a.jpg', content_base64: '' }))
.rejects.toThrow(/content_base64 is required/);
await expect(host.ctx.journal.addEntryPhoto(5, { name: 'a.jpg', content_base64: 'A'.repeat(15 * 1024 * 1024) }))
.rejects.toThrow(/10MB plugin upload cap/);
const ungranted = createMockHost({ grants: [], actingUserId: 42, journalEntries: [{ id: 5, journey_id: 1 }] });
await expect(ungranted.ctx.journal.addEntryPhoto(5, { name: 'a.jpg', content_base64: png }))
.rejects.toThrow(/PERMISSION_DENIED/);
});
it('creates a trip for the acting user and serves rates + collab reads against the grants', async () => {
const { ctx } = createMockHost({
grants: ['db:create:trips', 'rates:read', 'db:read:collab'],
@@ -29,6 +29,9 @@ describe('createPluginContext', () => {
await ctx.trips.getById(1);
expect(rpc).toHaveBeenCalledWith('trips.getById', { tripId: 1, _inv: 'inv-1' });
await ctx.journal.addEntryPhoto(5, { name: 'a.jpg', content_base64: 'eA==' });
expect(rpc).toHaveBeenCalledWith('journal.addEntryPhoto', { entryId: 5, input: { name: 'a.jpg', content_base64: 'eA==' }, _inv: 'inv-1' });
await ctx.ws.broadcastToTrip(1, 'ping', { a: 1 });
// carries _inv so the host can bind the acting user — without it the broadcast was refused
expect(rpc).toHaveBeenCalledWith('ws.broadcastToTrip', { tripId: 1, event: 'ping', data: { a: 1 }, _inv: 'inv-1' });