fix(library): skip book sync on olid search miss (identity-swap guard)

This commit is contained in:
afiqzudinhadi 2026-08-03 15:51:15 +08:00
parent 1c90ae9abc
commit 0c8f897e46
2 changed files with 17 additions and 3 deletions

View file

@ -77,7 +77,8 @@ export function buildBook(doc: any, prev: Record<string, string>): BookRecord {
/** Carries the legacy goodreads search link forward as a `## Links` entry across every re-render. */ /** Carries the legacy goodreads search link forward as a `## Links` entry across every re-render. */
function resolveGoodreadsLink(prev: Record<string, string>, body: string): string { function resolveGoodreadsLink(prev: Record<string, string>, body: string): string {
const fromBody = /\[Goodreads\]\(([^)]+)\)/.exec(body ?? ''); const linksMatch = /##\s*Links\s*\n([\s\S]*?)(?=\n##\s|$)/.exec(body ?? '');
const fromBody = linksMatch ? /\[Goodreads\]\(([^)]+)\)/.exec(linksMatch[1]) : null;
if (fromBody) return fromBody[1]; if (fromBody) return fromBody[1];
// first-pass skeleton conversion: legacy plain `url` field IS the goodreads link, before // first-pass skeleton conversion: legacy plain `url` field IS the goodreads link, before
// it gets overwritten by the canonical Open Library url // it gets overwritten by the canonical Open Library url
@ -176,8 +177,14 @@ export const bookSpec: MediaTypeSpec = {
deps.log(`book open library fetch failed (olid ${olid}): ${String(e)}`); deps.log(`book open library fetch failed (olid ${olid}): ${String(e)}`);
return null; return null;
} }
const doc = docs.find(d => String(d.key ?? '') === `/works/${olid}`) ?? docs[0]; if (docs.length === 0) return null;
if (!doc) return null; const doc = docs.find(d => String(d.key ?? '') === `/works/${olid}`);
if (!doc) {
// stored olid no longer in the search results -- never silently swap to a
// different work's data; leave the note untouched (id fields preserved)
deps.log(`book open library search returned no doc matching olid ${olid} (title "${query}")`);
return null;
}
const record = buildBook(doc, fm); const record = buildBook(doc, fm);
const goodreadsUrl = resolveGoodreadsLink(fm, ctx.body); const goodreadsUrl = resolveGoodreadsLink(fm, ctx.body);

View file

@ -215,6 +215,13 @@ describe('bookSpec.sync — open library enrich', () => {
expect(result).toBeNull(); expect(result).toBeNull();
}); });
test('olid search miss (results present, none match stored olid) -> null, no identity swap, logged', async () => {
const deps = makeDeps({ http: async () => ({ docs: [{ key: '/works/OL999999W', title: '1984', author_name: ['Someone Else'] }] }) });
const result = await bookSpec.sync(ctxFor({ olid: 'OL1168083W', title: '1984' }), deps);
expect(result).toBeNull();
expect(deps.logCalls.some(m => m.includes('OL1168083W'))).toBe(true);
});
test('successful enrich -> canonical fm + flipped always false', async () => { test('successful enrich -> canonical fm + flipped always false', async () => {
const deps = makeDeps({ http: async () => olFixture }); const deps = makeDeps({ http: async () => olFixture });
const fm = { olid: 'OL1168083W', title: '1984', read_status: 'Read', rating: '5', rating_stars: '⭐️⭐️⭐️⭐️⭐️' }; const fm = { olid: 'OL1168083W', title: '1984', read_status: 'Read', rating: '5', rating_stars: '⭐️⭐️⭐️⭐️⭐️' };