mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-07-27 04:49:49 -07:00
fix(anki): preserve known-word cache when maturity fetch fails
- Catch errors from fetchKnownWordMaturityTierSets so a failed tier query no longer aborts the whole refresh - Known words still cache; tiers just stay null until the next successful refresh - Add regression test covering findNotes failure on a tier query
This commit is contained in:
@@ -20,6 +20,7 @@ function createMaturityHarness(config: AnkiConnectConfig): {
|
|||||||
findNotesResult: number[];
|
findNotesResult: number[];
|
||||||
notesInfoResult: HarnessNoteInfo[];
|
notesInfoResult: HarnessNoteInfo[];
|
||||||
findNotesByQuery: Map<string, number[]>;
|
findNotesByQuery: Map<string, number[]>;
|
||||||
|
failedQueries: Set<string>;
|
||||||
};
|
};
|
||||||
createSiblingManager: () => KnownWordCacheManager;
|
createSiblingManager: () => KnownWordCacheManager;
|
||||||
cleanup: () => void;
|
cleanup: () => void;
|
||||||
@@ -31,12 +32,16 @@ function createMaturityHarness(config: AnkiConnectConfig): {
|
|||||||
findNotesResult: [] as number[],
|
findNotesResult: [] as number[],
|
||||||
notesInfoResult: [] as HarnessNoteInfo[],
|
notesInfoResult: [] as HarnessNoteInfo[],
|
||||||
findNotesByQuery: new Map<string, number[]>(),
|
findNotesByQuery: new Map<string, number[]>(),
|
||||||
|
failedQueries: new Set<string>(),
|
||||||
};
|
};
|
||||||
const deps = {
|
const deps = {
|
||||||
client: {
|
client: {
|
||||||
findNotes: async (query: string) => {
|
findNotes: async (query: string) => {
|
||||||
calls.findNotes += 1;
|
calls.findNotes += 1;
|
||||||
calls.queries.push(query);
|
calls.queries.push(query);
|
||||||
|
if (clientState.failedQueries.has(query)) {
|
||||||
|
throw new Error(`Anki unavailable for query: ${query}`);
|
||||||
|
}
|
||||||
if (clientState.findNotesByQuery.has(query)) {
|
if (clientState.findNotesByQuery.has(query)) {
|
||||||
return clientState.findNotesByQuery.get(query) ?? [];
|
return clientState.findNotesByQuery.get(query) ?? [];
|
||||||
}
|
}
|
||||||
@@ -220,7 +225,10 @@ test('reading-only fallback resolves tiers unless opted out', async () => {
|
|||||||
await manager.refresh(true);
|
await manager.refresh(true);
|
||||||
|
|
||||||
assert.equal(manager.getKnownWordTier('けいこく'), 'mature');
|
assert.equal(manager.getKnownWordTier('けいこく'), 'mature');
|
||||||
assert.equal(manager.getKnownWordTier('けいこく', undefined, { allowReadingOnlyMatch: false }), null);
|
assert.equal(
|
||||||
|
manager.getKnownWordTier('けいこく', undefined, { allowReadingOnlyMatch: false }),
|
||||||
|
null,
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
@@ -245,6 +253,29 @@ test('getKnownWordTier returns null and skips tier queries when maturity is disa
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('refresh preserves known-word cache when maturity lookup fails', async () => {
|
||||||
|
const { manager, statePath, clientState, cleanup } = createMaturityHarness(maturityConfig());
|
||||||
|
|
||||||
|
try {
|
||||||
|
clientState.findNotesByQuery.set('deck:"Mining"', [1]);
|
||||||
|
clientState.failedQueries.add('deck:"Mining" prop:ivl>=21');
|
||||||
|
clientState.notesInfoResult = [{ noteId: 1, fields: { Word: { value: '猫' } } }];
|
||||||
|
|
||||||
|
await manager.refresh(true);
|
||||||
|
|
||||||
|
assert.equal(manager.isKnownWord('猫'), true);
|
||||||
|
assert.equal(manager.getKnownWordTier('猫'), null);
|
||||||
|
const persisted = JSON.parse(fs.readFileSync(statePath, 'utf-8')) as {
|
||||||
|
version: number;
|
||||||
|
tiers: Record<string, string>;
|
||||||
|
};
|
||||||
|
assert.equal(persisted.version, 4);
|
||||||
|
assert.deepEqual(persisted.tiers, {});
|
||||||
|
} finally {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('tiers persist to v4 state and reload without refetching', async () => {
|
test('tiers persist to v4 state and reload without refetching', async () => {
|
||||||
const { manager, calls, statePath, clientState, createSiblingManager, cleanup } =
|
const { manager, calls, statePath, clientState, createSiblingManager, cleanup } =
|
||||||
createMaturityHarness(maturityConfig());
|
createMaturityHarness(maturityConfig());
|
||||||
|
|||||||
@@ -389,13 +389,18 @@ export class KnownWordCacheManager {
|
|||||||
this.isRefreshingKnownWords = true;
|
this.isRefreshingKnownWords = true;
|
||||||
try {
|
try {
|
||||||
const noteFieldsById = await this.fetchKnownWordNoteFieldsById();
|
const noteFieldsById = await this.fetchKnownWordNoteFieldsById();
|
||||||
const tierSets = this.isMaturityTrackingEnabled()
|
let tierSets = null;
|
||||||
? await fetchKnownWordMaturityTierSets(
|
if (this.isMaturityTrackingEnabled()) {
|
||||||
|
try {
|
||||||
|
tierSets = await fetchKnownWordMaturityTierSets(
|
||||||
(query, options) => this.deps.client.findNotes(query, options),
|
(query, options) => this.deps.client.findNotes(query, options),
|
||||||
this.getKnownWordQueryScopes().map((scope) => scope.query),
|
this.getKnownWordQueryScopes().map((scope) => scope.query),
|
||||||
getMatureIntervalThresholdDays(this.deps.getConfig()),
|
getMatureIntervalThresholdDays(this.deps.getConfig()),
|
||||||
)
|
);
|
||||||
: null;
|
} catch (error) {
|
||||||
|
log.warn('Failed to fetch known-word maturity tiers:', (error as Error).message);
|
||||||
|
}
|
||||||
|
}
|
||||||
const currentNoteIds = Array.from(noteFieldsById.keys()).sort((a, b) => a - b);
|
const currentNoteIds = Array.from(noteFieldsById.keys()).sort((a, b) => a - b);
|
||||||
|
|
||||||
if (this.noteEntriesById.size === 0) {
|
if (this.noteEntriesById.size === 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user