fix(kobo): refuse a replacement with the bytes the device already has

A replacement byte-identical to the device's copy was never downloaded, so
it stayed offered and every bookmark the device pushed was discarded.
Keep the current delivery instead, and tell the user in the replacement
panel that the device already has this copy.

Ref #1786
This commit is contained in:
neon
2026-10-17 00:10:32 +00:00
parent 5051ec0913
commit abb44aa47a
6 changed files with 61 additions and 6 deletions
@@ -439,6 +439,21 @@ describe('Read-aloud delivery contracts (e2e)', { timeout: 180_000 }, () => {
expect((await download()).rawPayload).toEqual(sourceBytes);
});
it('keeps the current delivery instead of offering a replacement with the bytes the device already has', async () => {
const device = (await request('POST', '/api/v1/kobo/devices', { name: 'Identical replacement' })).json();
await request('PATCH', '/api/v1/kobo/settings', { readAloudDelivery: 'audioless_epub' });
const base = `/api/v1/kobo/devices/${device.id}`;
expect((await ctx.app.inject({ method: 'GET', url: `/api/v1/kobo/${device.token}/v1/books/${book.bookId}/download` })).statusCode).toBe(200);
const delivered = (await request('GET', `${base}/books/${book.bookId}/delivery`)).json();
const prepared = await request('POST', `${base}/books/${book.bookId}/replacement`, { variant: 'audioless_epub' });
expect(prepared.statusCode).toBe(201);
expect(prepared.json()).toMatchObject({ id: delivered.id, options: { replacement: false } });
const rows = await ctx.db.query.koboDeviceDeliveries.findMany({ where: eq(schema.koboDeviceDeliveries.deviceId, device.id) });
expect(rows).toHaveLength(1);
});
describe('Storyteller progress-reset regressions over live HTTP', () => {
let origin: string;
const cfi = 'epubcfi(/6/2!/4/2/2/1:3)';