fix(replication): flag additional Vault reflection failures

This commit is contained in:
Artem Ovchinnikov committed 2026-10-21 21:39:53 +03:00
1 parent 95c7d6cb69
commit d527fa9764
2 files changed
+28 -1

No files matched your search

@@ -521,6 +521,8 @@ export class ReplicateResultProcessor {
releaser = await this._semaphore.acquire(); releaser = await this._semaphore.acquire();
await this._applyToDatabase(doc); await this._applyToDatabase(doc);
} catch (e) { } catch (e) {
// An exception during application must not be mistaken for a fully reflected Vault.
this.context.onVaultReflectionFailure?.();
this.log(`Error while processing replication result`, LOG_LEVEL_NOTICE); this.log(`Error while processing replication result`, LOG_LEVEL_NOTICE);
this.logError(e); this.logError(e);
} finally { } finally {
@@ -630,6 +632,7 @@ export class ReplicateResultProcessor {
// Note that this is not a common case. // Note that this is not a common case.
return true; return true;
} else { } else {
this.context.onVaultReflectionFailure?.();
this.log( this.log(
`Failed to get existing document for ${path} (${shortenId(dbDoc._id)}, ${shortenRev(dbDoc._rev)}) `, `Failed to get existing document for ${path} (${shortenId(dbDoc._id)}, ${shortenRev(dbDoc._rev)}) `,
LOG_LEVEL_NOTICE LOG_LEVEL_NOTICE
@@ -38,6 +38,8 @@ function note(id: string): PouchDB.Core.ExistingDocument<EntryDoc> {
type SetupOptions = { type SetupOptions = {
onVaultReflectionFailure?: () => void; onVaultReflectionFailure?: () => void;
getRaw?: (id: string) => Promise<unknown>;
getDBEntryFromMeta?: (entry: object) => Promise<unknown>;
applicationReady?: boolean; applicationReady?: boolean;
databaseReady?: boolean; databaseReady?: boolean;
maxMTimeForReflectEvents?: number; maxMTimeForReflectEvents?: number;
@@ -58,7 +60,7 @@ function setup(options: SetupOptions = {}) {
const onCloseActiveReplication = vi.fn(async () => true); const onCloseActiveReplication = vi.fn(async () => true);
const isReady = vi.fn(() => options.applicationReady ?? true); const isReady = vi.fn(() => options.applicationReady ?? true);
const isValidPath = vi.fn(options.isValidPath ?? (() => true)); const isValidPath = vi.fn(options.isValidPath ?? (() => true));
const getDBEntryFromMeta = vi.fn(async (entry: object) => ({ ...entry, data: "x" })); const getDBEntryFromMeta = vi.fn(options.getDBEntryFromMeta ?? (async (entry: object) => ({ ...entry, data: "x" })));
const localPhysicalDatabase = { const localPhysicalDatabase = {
...(options.databaseId ? { id: vi.fn(async () => options.databaseId) } : {}), ...(options.databaseId ? { id: vi.fn(async () => options.databaseId) } : {}),
} as PouchDB.Database<EntryDoc>; } as PouchDB.Database<EntryDoc>;
@@ -90,6 +92,7 @@ function setup(options: SetupOptions = {}) {
if (options.localVersionInfo === undefined) throw { status: 404 }; if (options.localVersionInfo === undefined) throw { status: 404 };
return options.localVersionInfo; return options.localVersionInfo;
} }
if (options.getRaw) return await options.getRaw(id);
return { _id: id, _rev: "1-test" }; return { _id: id, _rev: "1-test" };
}), }),
getDBEntryFromMeta, getDBEntryFromMeta,
@@ -126,6 +129,27 @@ function setup(options: SetupOptions = {}) {
} }
describe("ReplicateResultProcessor", () => { describe("ReplicateResultProcessor", () => {
it("reports an IndexedDB read error as failed reflection", async () => {
const onVaultReflectionFailure = vi.fn();
const { processor, processSynchroniseResult } = setup({
onVaultReflectionFailure,
getRaw: async () => { throw new Error("IndexedDB read failed"); },
});
processor.enqueueAll([note("unreadable")]);
await vi.waitFor(() => expect(onVaultReflectionFailure).toHaveBeenCalledOnce());
expect(processSynchroniseResult).not.toHaveBeenCalled();
});
it("reports a thrown document reconstruction as failed reflection", async () => {
const onVaultReflectionFailure = vi.fn();
const { processor } = setup({
onVaultReflectionFailure,
getDBEntryFromMeta: async () => { throw new Error("Cannot read chunks"); },
});
processor.enqueueAll([note("missing-chunks")]);
await vi.waitFor(() => expect(onVaultReflectionFailure).toHaveBeenCalledOnce());
});
it("publishes pending received documents synchronously even if snapshot persistence stalls", () => { it("publishes pending received documents synchronously even if snapshot persistence stalls", () => {
const { processor, replicationResultCount, processSynchroniseResult } = setup({ const { processor, replicationResultCount, processSynchroniseResult } = setup({
applicationReady: false, applicationReady: false,