mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-11-03 14:58:18 +00:00
Retire legacy purge-based cleanup recovery
This commit is contained in:
1 parent
f325d2454d
commit
d1af8a5dfa
4 files changed
+129
-223
No files matched your search
@@ -1,48 +1,22 @@
|
||||
import {
|
||||
VERSIONING_DOCID,
|
||||
type EntryDoc,
|
||||
type ObsidianLiveSyncSettings,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import type { ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import { assessTweakCompatibility } from "@vrtmrz/livesync-commonlib/settings";
|
||||
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, Logger } from "octagonal-wheels/common/logger";
|
||||
import { skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
|
||||
import { balanceChunkPurgedDBs, purgeUnreferencedChunks } from "@vrtmrz/livesync-commonlib/compat/pouchdb/chunks";
|
||||
import { LiveSyncCouchDBReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
|
||||
import {
|
||||
CENTRAL_COMPATIBILITY_REJECTION_REASONS,
|
||||
REPLICATION_PROGRESS_PRESENTATIONS,
|
||||
assessRemoteFeatureDocument,
|
||||
describeRemoteFeatureRejection,
|
||||
type ReplicatorInstance,
|
||||
type ReplicationFailureRequest,
|
||||
} from "@vrtmrz/livesync-commonlib/replication";
|
||||
import { $msg } from "@/common/translation";
|
||||
import { usesLegacyIndexedDBAdapter } from "@/common/compatibilitySettings";
|
||||
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
||||
import type PouchDB from "pouchdb-core";
|
||||
|
||||
async function canInterpretCleanupDatabase(db: PouchDB.Database<EntryDoc>): Promise<boolean> {
|
||||
try {
|
||||
const assessment = assessRemoteFeatureDocument(await db.get(VERSIONING_DOCID));
|
||||
if (assessment.status === "supported" || assessment.status === "older-generation") return true;
|
||||
Logger(`Database cleanup cancelled: ${describeRemoteFeatureRejection(assessment)}`, LOG_LEVEL_NOTICE);
|
||||
} catch (error) {
|
||||
Logger("Database cleanup cancelled: feature compatibility could not be checked.", LOG_LEVEL_NOTICE);
|
||||
Logger(error, LOG_LEVEL_INFO);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
type CentralCompatibilityRecoveryServices = Pick<
|
||||
LiveSyncBaseCore["services"],
|
||||
"API" | "appLifecycle" | "replicator" | "setting" | "tweakValue"
|
||||
"appLifecycle" | "replicator" | "setting" | "tweakValue"
|
||||
>;
|
||||
|
||||
/** Collaborators for applying a compatibility decision to its failed publication. */
|
||||
interface CentralCompatibilityRecoveryContext {
|
||||
readonly confirm: LiveSyncBaseCore["confirm"];
|
||||
/** Obtain the database only when recovery runs, after initialisation or reset. */
|
||||
readonly getLocalDatabase: () => LiveSyncBaseCore["localDatabase"];
|
||||
readonly rebuilder: LiveSyncBaseCore["rebuilder"];
|
||||
readonly services: CentralCompatibilityRecoveryServices;
|
||||
}
|
||||
@@ -72,89 +46,11 @@ function canMarkRemoteResolved(replicator: ReplicatorInstance): replicator is Re
|
||||
* replacement; the failure result is never re-read from the current instance.
|
||||
*/
|
||||
export function createCentralCompatibilityRecovery(context: CentralCompatibilityRecoveryContext) {
|
||||
async function reconcileCleanedRemote(
|
||||
showProgress: boolean,
|
||||
setting: ObsidianLiveSyncSettings,
|
||||
expectedContext: ReplicationFailureRequest["context"]
|
||||
) {
|
||||
Logger("The remote database has been cleaned.", showProgress ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
|
||||
await skipIfDuplicated("cleanup", async () => {
|
||||
if (!(await canInterpretCleanupDatabase(context.getLocalDatabase().localDatabase))) return;
|
||||
const count = await purgeUnreferencedChunks(context.getLocalDatabase().localDatabase, true);
|
||||
const message = `The remote database has been cleaned up.
|
||||
To synchronize, this device must be also cleaned up. ${count} chunk(s) will be erased from this device.
|
||||
However, If there are many chunks to be deleted, maybe fetching again is faster.
|
||||
We will lose the history of this device if we fetch the remote database again.
|
||||
Even if you choose to clean up, you will see this option again if you exit Obsidian and then synchronise again.`;
|
||||
const CHOICE_FETCH = "Fetch again";
|
||||
const CHOICE_CLEAN = "Cleanup";
|
||||
const CHOICE_DISMISS = "Dismiss";
|
||||
const selected = await context.confirm.confirmWithMessage(
|
||||
"Cleaned",
|
||||
message,
|
||||
[CHOICE_FETCH, CHOICE_CLEAN, CHOICE_DISMISS],
|
||||
CHOICE_DISMISS,
|
||||
30
|
||||
);
|
||||
if (selected == CHOICE_FETCH) {
|
||||
await context.rebuilder.$performRebuildDB("localOnly");
|
||||
}
|
||||
if (selected != CHOICE_CLEAN) return;
|
||||
|
||||
await context.services.replicator.runBoundedRemoteActivity(
|
||||
() =>
|
||||
context.services.replicator.runWithActiveReplicatorContext(async (activeContext) => {
|
||||
if (activeContext !== expectedContext) return;
|
||||
const replicator = activeContext.replicator;
|
||||
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return;
|
||||
const localDatabase = context.getLocalDatabase();
|
||||
const remoteDatabase = await replicator.connectRemoteCouchDBWithSetting(
|
||||
setting,
|
||||
context.services.API.isMobile(),
|
||||
true
|
||||
);
|
||||
if (typeof remoteDatabase == "string") {
|
||||
Logger(remoteDatabase, LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await purgeUnreferencedChunks(localDatabase.localDatabase, false);
|
||||
localDatabase.clearCaches();
|
||||
const replicated = await context.services.replicator.runFiniteReplicationActivity(
|
||||
() => replicator.openOneShotReplication(setting, showProgress, false, "sync", true),
|
||||
{ label: "replication" }
|
||||
);
|
||||
if (replicated) {
|
||||
await balanceChunkPurgedDBs(localDatabase.localDatabase, remoteDatabase.db);
|
||||
await purgeUnreferencedChunks(localDatabase.localDatabase, false);
|
||||
localDatabase.clearCaches();
|
||||
await replicator.markRemoteResolved(setting);
|
||||
Logger(
|
||||
"The local database has been cleaned up.",
|
||||
showProgress ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
|
||||
);
|
||||
} else {
|
||||
Logger(
|
||||
"Replication has been cancelled. Please try it again.",
|
||||
showProgress ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
await remoteDatabase.close();
|
||||
}
|
||||
}),
|
||||
{ label: "database-cleanup" }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async function handleReplicationFailure(request: ReplicationFailureRequest): Promise<boolean> {
|
||||
const { context: failedContext, interaction, outcome, progressPresentation, setting } = request;
|
||||
const showProgress = progressPresentation === REPLICATION_PROGRESS_PRESENTATIONS.NOTICE;
|
||||
const { context: failedContext, interaction, outcome, setting } = request;
|
||||
if (interaction.kind === "forbidden") {
|
||||
// Automatic requests may report the failure, but must not enter
|
||||
// tweak, lock, fetch, unlock, or cleanup dialogues.
|
||||
// tweak, lock, fetch, or unlock dialogues.
|
||||
Logger("Replication failed on an unattended path.", LOG_LEVEL_INFO);
|
||||
return false;
|
||||
}
|
||||
@@ -198,14 +94,6 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
recovery.reason === CENTRAL_COMPATIBILITY_REJECTION_REASONS.NODE_CLEANED &&
|
||||
usesLegacyIndexedDBAdapter(setting)
|
||||
) {
|
||||
await reconcileCleanedRemote(showProgress, setting, failedContext);
|
||||
return false;
|
||||
}
|
||||
|
||||
const message = $msg("Replicator.Dialogue.Locked.Message");
|
||||
const CHOICE_FETCH = $msg("Replicator.Dialogue.Locked.Action.Fetch");
|
||||
const CHOICE_DISMISS = $msg("Replicator.Dialogue.Locked.Action.Dismiss");
|
||||
@@ -240,5 +128,5 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
||||
return false;
|
||||
}
|
||||
|
||||
return Object.freeze({ handleReplicationFailure, reconcileCleanedRemote });
|
||||
return Object.freeze({ handleReplicationFailure });
|
||||
}
|
||||
@@ -16,55 +16,86 @@ const chunkMocks = vi.hoisted(() => ({
|
||||
}));
|
||||
|
||||
vi.mock("@vrtmrz/livesync-commonlib/compat/pouchdb/chunks", () => chunkMocks);
|
||||
vi.mock("@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator", () => ({
|
||||
LiveSyncCouchDBReplicator: class {},
|
||||
}));
|
||||
|
||||
import { LiveSyncCouchDBReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
|
||||
import { createCentralCompatibilityRecovery } from "./centralCompatibilityRecovery";
|
||||
|
||||
describe("central compatibility recovery", () => {
|
||||
it("does not count chunks for cleanup when local feature requirements are unknown", async () => {
|
||||
chunkMocks.purgeUnreferencedChunks.mockClear();
|
||||
const confirmWithMessage = vi.fn(async () => "Dismiss");
|
||||
const recovery = createCentralCompatibilityRecovery({
|
||||
confirm: { confirmWithMessage },
|
||||
getLocalDatabase: () => ({
|
||||
localDatabase: {
|
||||
get: vi.fn(async (id: string) => ({
|
||||
_id: id,
|
||||
type: "versioninfo",
|
||||
version: 13,
|
||||
used_features: ["future-format-v7"],
|
||||
})),
|
||||
},
|
||||
}),
|
||||
services: { replicator: {} },
|
||||
} as never);
|
||||
describe.each([
|
||||
{ name: "unknown local features", version: 13, used_features: ["future-format-v7"] },
|
||||
{ name: "a legacy local version", version: 11 },
|
||||
{ name: "the current local version", version: 12 },
|
||||
])("compatibility: cleaned IndexedDB remote with $name", (localVersion) => {
|
||||
it.each(["fetch", "unlock", "dismiss"] as const)(
|
||||
"uses standard recovery for an explicit %s choice without legacy cleanup",
|
||||
async (choice) => {
|
||||
chunkMocks.purgeUnreferencedChunks.mockClear();
|
||||
chunkMocks.balanceChunkPurgedDBs.mockClear();
|
||||
const markRemoteResolved = vi.fn(async (_setting: unknown) => undefined);
|
||||
const failedContext = {
|
||||
provider: {},
|
||||
replicator: { markRemoteResolved },
|
||||
configurationIdentity: "cleaned-remote-test",
|
||||
};
|
||||
const setting = { useIndexedDBAdapter: true } as ObsidianLiveSyncSettings;
|
||||
const scheduleFetch = vi.fn(async () => undefined);
|
||||
const scheduleRestart = vi.fn();
|
||||
const getLocalDatabase = vi.fn(() => ({
|
||||
localDatabase: {
|
||||
get: vi.fn(async () => ({ _id: VERSIONING_DOCID, type: "versioninfo", ...localVersion })),
|
||||
},
|
||||
}));
|
||||
const confirmWithMessage = vi.fn(async () => "Dismiss");
|
||||
const askSelectStringDialogue = vi.fn(
|
||||
async (_message: string, choices: string[]) =>
|
||||
choices[choice === "fetch" ? 0 : choice === "unlock" ? 1 : 2]
|
||||
);
|
||||
const recovery = createCentralCompatibilityRecovery({
|
||||
confirm: { askSelectStringDialogue, confirmWithMessage },
|
||||
getLocalDatabase,
|
||||
rebuilder: { scheduleFetch },
|
||||
services: {
|
||||
appLifecycle: { scheduleRestart },
|
||||
replicator: {
|
||||
runWithActiveReplicatorContext: async (task: (context: unknown) => unknown) =>
|
||||
await task(failedContext),
|
||||
},
|
||||
},
|
||||
} as never);
|
||||
|
||||
await recovery.reconcileCleanedRemote(true, {} as ObsidianLiveSyncSettings, {} as never);
|
||||
const result = await recovery.handleReplicationFailure({
|
||||
context: failedContext,
|
||||
setting,
|
||||
outcome: replicationFailed(new Error("cleaned"), {
|
||||
reason: CENTRAL_COMPATIBILITY_REJECTION_REASONS.NODE_CLEANED,
|
||||
}),
|
||||
progressPresentation: REPLICATION_PROGRESS_PRESENTATIONS.NOTICE,
|
||||
interaction: USER_INITIATED_REPLICATION_AUTHORITY,
|
||||
} as never);
|
||||
|
||||
expect(chunkMocks.purgeUnreferencedChunks).not.toHaveBeenCalled();
|
||||
expect(confirmWithMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows cleanup counting for a legacy local version document", async () => {
|
||||
chunkMocks.purgeUnreferencedChunks.mockClear();
|
||||
const confirmWithMessage = vi.fn(async () => "Dismiss");
|
||||
const recovery = createCentralCompatibilityRecovery({
|
||||
confirm: { confirmWithMessage },
|
||||
getLocalDatabase: () => ({
|
||||
localDatabase: {
|
||||
get: vi.fn(async (id: string) => ({ _id: id, type: "versioninfo", version: 11 })),
|
||||
},
|
||||
}),
|
||||
services: { replicator: {} },
|
||||
} as never);
|
||||
|
||||
await recovery.reconcileCleanedRemote(true, {} as ObsidianLiveSyncSettings, {} as never);
|
||||
|
||||
expect(chunkMocks.purgeUnreferencedChunks).toHaveBeenCalledWith(expect.anything(), true);
|
||||
expect(confirmWithMessage).toHaveBeenCalledOnce();
|
||||
expect(result).toBe(false);
|
||||
expect(askSelectStringDialogue).toHaveBeenCalledOnce();
|
||||
const choices = askSelectStringDialogue.mock.calls[0][1];
|
||||
expect(choices).toHaveLength(3);
|
||||
expect(askSelectStringDialogue).toHaveBeenCalledWith(expect.any(String), choices, {
|
||||
title: expect.any(String),
|
||||
defaultAction: choices[2],
|
||||
timeout: 60,
|
||||
});
|
||||
expect(confirmWithMessage).not.toHaveBeenCalled();
|
||||
expect(getLocalDatabase).not.toHaveBeenCalled();
|
||||
expect(chunkMocks.purgeUnreferencedChunks).not.toHaveBeenCalled();
|
||||
expect(chunkMocks.balanceChunkPurgedDBs).not.toHaveBeenCalled();
|
||||
expect(scheduleFetch).toHaveBeenCalledTimes(choice === "fetch" ? 1 : 0);
|
||||
expect(scheduleRestart).toHaveBeenCalledTimes(choice === "fetch" ? 1 : 0);
|
||||
expect(markRemoteResolved).toHaveBeenCalledTimes(choice === "unlock" ? 1 : 0);
|
||||
if (choice === "fetch") {
|
||||
expect(scheduleFetch.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
scheduleRestart.mock.invocationCallOrder[0]
|
||||
);
|
||||
}
|
||||
if (choice === "unlock") expect(markRemoteResolved).toHaveBeenCalledWith(setting);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("passes the failed attempt's exact tweak assessment to mismatch resolution", async () => {
|
||||
@@ -139,7 +170,6 @@ describe("central compatibility recovery", () => {
|
||||
try {
|
||||
const recovery = createCentralCompatibilityRecovery({
|
||||
confirm: {},
|
||||
getLocalDatabase: () => ({}),
|
||||
rebuilder: {},
|
||||
services: {
|
||||
appLifecycle: {},
|
||||
@@ -187,7 +217,6 @@ describe("central compatibility recovery", () => {
|
||||
});
|
||||
const recovery = createCentralCompatibilityRecovery({
|
||||
confirm: {},
|
||||
getLocalDatabase: () => ({}),
|
||||
rebuilder: {},
|
||||
services: {
|
||||
appLifecycle: {},
|
||||
@@ -255,7 +284,6 @@ describe("central compatibility recovery", () => {
|
||||
);
|
||||
const recovery = createCentralCompatibilityRecovery({
|
||||
confirm: {},
|
||||
getLocalDatabase: () => ({}),
|
||||
rebuilder: {},
|
||||
services: {
|
||||
appLifecycle: {},
|
||||
@@ -287,7 +315,10 @@ describe("central compatibility recovery", () => {
|
||||
expect(setPreferredRemoteTweakSettings.mock.calls[0][0]).not.toBe(effectiveSetting);
|
||||
});
|
||||
|
||||
it("does not apply an unlock selected for a replaced failed publication", async () => {
|
||||
it.each([
|
||||
CENTRAL_COMPATIBILITY_REJECTION_REASONS.NODE_LOCKED,
|
||||
CENTRAL_COMPATIBILITY_REJECTION_REASONS.NODE_CLEANED,
|
||||
])("does not apply an unlock for a replaced failed publication: %s", async (reason) => {
|
||||
const failedMarkResolved = vi.fn(async () => undefined);
|
||||
const replacementMarkResolved = vi.fn(async () => undefined);
|
||||
const failedContext = { provider: {}, replicator: { markRemoteResolved: failedMarkResolved } };
|
||||
@@ -299,7 +330,6 @@ describe("central compatibility recovery", () => {
|
||||
confirm: {
|
||||
askSelectStringDialogue: vi.fn(async (_message: string, choices: string[]) => choices[1]),
|
||||
},
|
||||
getLocalDatabase: () => ({}),
|
||||
rebuilder: {},
|
||||
services: {
|
||||
appLifecycle: { scheduleRestart: vi.fn() },
|
||||
@@ -311,9 +341,9 @@ describe("central compatibility recovery", () => {
|
||||
|
||||
await recovery.handleReplicationFailure({
|
||||
context: failedContext,
|
||||
setting: {},
|
||||
setting: { useIndexedDBAdapter: true },
|
||||
outcome: replicationFailed(new Error("locked"), {
|
||||
reason: CENTRAL_COMPATIBILITY_REJECTION_REASONS.NODE_LOCKED,
|
||||
reason,
|
||||
}),
|
||||
progressPresentation: REPLICATION_PROGRESS_PRESENTATIONS.NOTICE,
|
||||
interaction: USER_INITIATED_REPLICATION_AUTHORITY,
|
||||
@@ -324,69 +354,55 @@ describe("central compatibility recovery", () => {
|
||||
expect(replacementMarkResolved).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps cleaned-remote replication and balancing inside the shared activity boundary", async () => {
|
||||
const activityFinished = vi.fn();
|
||||
const runBoundedRemoteActivity = vi.fn(async (task: () => unknown) => {
|
||||
try {
|
||||
return await task();
|
||||
} finally {
|
||||
activityFinished();
|
||||
}
|
||||
});
|
||||
const runFiniteReplicationActivity = vi.fn(async (task: () => unknown) => await task());
|
||||
const openOneShotReplication = vi.fn(async () => true);
|
||||
const remoteDatabase = {
|
||||
close: vi.fn(async () => undefined),
|
||||
it.each([
|
||||
NO_INTERACTION,
|
||||
{
|
||||
kind: "permitted" as const,
|
||||
permissions: { ...USER_INITIATED_REPLICATION_AUTHORITY.permissions, failureRecovery: false },
|
||||
},
|
||||
])("does not recover a cleaned remote without recovery authority: %j", async (interaction) => {
|
||||
const askSelectStringDialogue = vi.fn();
|
||||
const confirmWithMessage = vi.fn();
|
||||
const getLocalDatabase = vi.fn();
|
||||
const scheduleFetch = vi.fn();
|
||||
const scheduleRestart = vi.fn();
|
||||
const markRemoteResolved = vi.fn();
|
||||
const failedContext = {
|
||||
provider: {},
|
||||
replicator: { markRemoteResolved },
|
||||
configurationIdentity: "cleaned-remote-test",
|
||||
};
|
||||
const close = vi.fn(async () => undefined);
|
||||
const activeReplicator = Object.assign(new LiveSyncCouchDBReplicator({} as never), {
|
||||
connectRemoteCouchDBWithSetting: vi.fn(async () => ({ db: remoteDatabase, close })),
|
||||
openOneShotReplication,
|
||||
markRemoteResolved: vi.fn(async () => undefined),
|
||||
});
|
||||
const expectedContext = { provider: {}, replicator: activeReplicator };
|
||||
const runWithActiveReplicatorContext = vi.fn(async (task: (context: unknown) => unknown) =>
|
||||
task(expectedContext)
|
||||
task(failedContext)
|
||||
);
|
||||
const localDatabase = {
|
||||
localDatabase: {
|
||||
get: vi.fn(async () => ({ _id: VERSIONING_DOCID, type: "versioninfo", version: 12 })),
|
||||
},
|
||||
clearCaches: vi.fn(),
|
||||
};
|
||||
const getLocalDatabase = vi.fn(() => localDatabase);
|
||||
const recovery = createCentralCompatibilityRecovery({
|
||||
confirm: { confirmWithMessage: vi.fn(async () => "Cleanup") },
|
||||
confirm: { askSelectStringDialogue, confirmWithMessage },
|
||||
getLocalDatabase,
|
||||
rebuilder: {},
|
||||
rebuilder: { scheduleFetch },
|
||||
services: {
|
||||
appLifecycle: {},
|
||||
API: { isMobile: vi.fn(() => false) },
|
||||
replicator: {
|
||||
runBoundedRemoteActivity,
|
||||
runFiniteReplicationActivity,
|
||||
runWithActiveReplicatorContext,
|
||||
},
|
||||
tweakValue: {},
|
||||
appLifecycle: { scheduleRestart },
|
||||
replicator: { runWithActiveReplicatorContext },
|
||||
},
|
||||
} as never);
|
||||
|
||||
await recovery.reconcileCleanedRemote(true, {} as ObsidianLiveSyncSettings, expectedContext as never);
|
||||
await expect(
|
||||
recovery.handleReplicationFailure({
|
||||
context: failedContext,
|
||||
setting: { useIndexedDBAdapter: true },
|
||||
outcome: replicationFailed(new Error("cleaned"), {
|
||||
reason: CENTRAL_COMPATIBILITY_REJECTION_REASONS.NODE_CLEANED,
|
||||
}),
|
||||
progressPresentation: REPLICATION_PROGRESS_PRESENTATIONS.NOTICE,
|
||||
interaction,
|
||||
} as never)
|
||||
).resolves.toBe(false);
|
||||
|
||||
expect(runBoundedRemoteActivity).toHaveBeenCalledWith(expect.any(Function), {
|
||||
label: "database-cleanup",
|
||||
});
|
||||
expect(runFiniteReplicationActivity).toHaveBeenCalledWith(expect.any(Function), {
|
||||
label: "replication",
|
||||
});
|
||||
expect(runWithActiveReplicatorContext).toHaveBeenCalledOnce();
|
||||
expect(openOneShotReplication).toHaveBeenCalledOnce();
|
||||
expect(openOneShotReplication.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
activityFinished.mock.invocationCallOrder[0]
|
||||
);
|
||||
expect(chunkMocks.balanceChunkPurgedDBs).toHaveBeenCalledOnce();
|
||||
expect(getLocalDatabase).toHaveBeenCalled();
|
||||
expect(close).toHaveBeenCalledOnce();
|
||||
expect(close.mock.invocationCallOrder[0]).toBeLessThan(activityFinished.mock.invocationCallOrder[0]);
|
||||
expect(askSelectStringDialogue).not.toHaveBeenCalled();
|
||||
expect(confirmWithMessage).not.toHaveBeenCalled();
|
||||
expect(getLocalDatabase).not.toHaveBeenCalled();
|
||||
expect(scheduleFetch).not.toHaveBeenCalled();
|
||||
expect(scheduleRestart).not.toHaveBeenCalled();
|
||||
expect(runWithActiveReplicatorContext).not.toHaveBeenCalled();
|
||||
expect(markRemoteResolved).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -83,10 +83,8 @@ export function useReplicationFeature<TContext extends ServiceContext, TCommands
|
||||
const securitySeedPreflight = createSecuritySeedPreflight(unresolvedErrorManager, preflightContext);
|
||||
const centralCompatibilityRecovery = createCentralCompatibilityRecovery({
|
||||
confirm: core.confirm,
|
||||
getLocalDatabase: () => core.localDatabase,
|
||||
rebuilder: core.rebuilder,
|
||||
services: {
|
||||
API: services.API,
|
||||
appLifecycle: services.appLifecycle,
|
||||
replicator: services.replicator,
|
||||
setting: services.setting,
|
||||
|
||||
Reference in new issue
Block a user