import { describe, expect, it, vi } from "vitest"; import { REMOTE_COUCHDB, REMOTE_MINIO } from "@vrtmrz/livesync-commonlib/compat/common/types"; import { createNewVaultSettings } from "@vrtmrz/livesync-commonlib/settings"; import { CAPABILITY_SUPPORT_KINDS, NO_INTERACTION, REPLICATION_COMPLETED, REPLICATION_PROGRESS_PRESENTATIONS, REMOTE_RESOURCE_KINDS, USER_INITIATED_REPLICATION_AUTHORITY, } from "@vrtmrz/livesync-commonlib/replication"; const constructorMocks = vi.hoisted(() => ({ couchDB: vi.fn(), couchDBOneShot: vi.fn(async (..._args: unknown[]) => REPLICATION_COMPLETED), objectStorage: vi.fn(), objectStorageOneShot: vi.fn(async (..._args: unknown[]) => REPLICATION_COMPLETED), })); vi.mock("@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator", () => ({ LiveSyncCouchDBReplicator: class { constructor(host: unknown) { constructorMocks.couchDB(host); } openOneShotReplicationWithOutcome(...args: unknown[]) { return constructorMocks.couchDBOneShot(...args); } }, })); vi.mock("@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicator", () => ({ LiveSyncJournalReplicator: class { constructor(host: unknown) { constructorMocks.objectStorage(host); } openOneShotReplicationWithOutcome(...args: unknown[]) { return constructorMocks.objectStorageOneShot(...args); } }, })); import { createCentralReplicatorProviderDefinitions } from "./replicatorProviders"; describe("central Replicator provider definitions", () => { it("keeps the retained remote-resource catalogue bounded", () => { expect .soft(Object.values(REMOTE_RESOURCE_KINDS).sort()) .toEqual(["connection", "preferred-tweak", "security-seed", "synchronisation-information"].sort()); }); it("composes CouchDB and Object Storage policies outside LiveSyncBaseCore", async () => { const host = {} as Parameters[0]; const definitions = createCentralReplicatorProviderDefinitions(host); const couchDB = definitions.get(REMOTE_COUCHDB)!; const objectStorage = definitions.get(REMOTE_MINIO)!; expect([...definitions.keys()]).toEqual([REMOTE_COUCHDB, REMOTE_MINIO]); expect("sameKindReconciliation" in couchDB).toBe(false); expect("sameKindReconciliation" in objectStorage).toBe(false); expect( couchDB.isConfigured( Object.assign(createNewVaultSettings(), { remoteType: REMOTE_COUCHDB, couchDB_URI: "https://couch.example.test", couchDB_DBNAME: "vault", }) ) ).toBe(true); expect( objectStorage.isConfigured( Object.assign(createNewVaultSettings(), { remoteType: REMOTE_MINIO, endpoint: "https://objects.example.test", bucket: "vault", }) ) ).toBe(true); await couchDB.create(createNewVaultSettings()); await objectStorage.create(createNewVaultSettings()); expect(constructorMocks.couchDB).toHaveBeenCalledWith(host); expect(constructorMocks.objectStorage).toHaveBeenCalledWith(host); }); it("rejects incomplete and wrong-kind settings before construction", () => { const definitions = createCentralReplicatorProviderDefinitions({} as never); const couchDB = definitions.get(REMOTE_COUCHDB)!; const objectStorage = definitions.get(REMOTE_MINIO)!; expect(couchDB.isConfigured(Object.assign(createNewVaultSettings(), { remoteType: REMOTE_MINIO }))).toBe(false); expect( objectStorage.isConfigured(Object.assign(createNewVaultSettings(), { remoteType: REMOTE_COUCHDB })) ).toBe(false); }); it("declares the retained owned resources and cohesive optional administration", () => { const definitions = createCentralReplicatorProviderDefinitions({} as never); const couchResources = definitions.get(REMOTE_COUCHDB)?.remoteResources; const objectResources = definitions.get(REMOTE_MINIO)?.remoteResources; const couchAdministration = definitions.get(REMOTE_COUCHDB)?.centralRemoteAdministration; const objectAdministration = definitions.get(REMOTE_MINIO)?.centralRemoteAdministration; expect(Object.keys(couchResources ?? {}).sort()).toEqual(Object.values(REMOTE_RESOURCE_KINDS).sort()); expect(Object.keys(objectResources ?? {}).sort()).toEqual(Object.values(REMOTE_RESOURCE_KINDS).sort()); expect(couchResources?.[REMOTE_RESOURCE_KINDS.CONNECTION].kind).toBe(CAPABILITY_SUPPORT_KINDS.SUPPORTED); expect(couchResources?.[REMOTE_RESOURCE_KINDS.PREFERRED_TWEAK].kind).toBe(CAPABILITY_SUPPORT_KINDS.SUPPORTED); expect(couchResources?.[REMOTE_RESOURCE_KINDS.SECURITY_SEED].kind).toBe(CAPABILITY_SUPPORT_KINDS.SUPPORTED); expect(couchResources?.[REMOTE_RESOURCE_KINDS.SYNCHRONISATION_INFORMATION].kind).toBe( CAPABILITY_SUPPORT_KINDS.SUPPORTED ); expect(objectResources?.[REMOTE_RESOURCE_KINDS.SECURITY_SEED].kind).toBe(CAPABILITY_SUPPORT_KINDS.SUPPORTED); expect(objectResources?.[REMOTE_RESOURCE_KINDS.SYNCHRONISATION_INFORMATION].kind).toBe( CAPABILITY_SUPPORT_KINDS.NOT_APPLICABLE ); expect(couchAdministration?.kind).toBe(CAPABILITY_SUPPORT_KINDS.SUPPORTED); expect(objectAdministration?.kind).toBe(CAPABILITY_SUPPORT_KINDS.SUPPORTED); expect("activeRemoteReads" in definitions.get(REMOTE_COUCHDB)!).toBe(false); expect("fullTransfers" in definitions.get(REMOTE_COUCHDB)!).toBe(false); }); it("dispatches central finite work through provider-local attempt results", async () => { const definitions = createCentralReplicatorProviderDefinitions({} as never); const couchDB = definitions.get(REMOTE_COUCHDB)!; const objectStorage = definitions.get(REMOTE_MINIO)!; const setting = createNewVaultSettings(); const couchInstance = await couchDB.create(setting); const objectInstance = await objectStorage.create(setting); if (!couchInstance || !objectInstance) throw new Error("Provider construction failed"); if (couchDB.userInitiatedOneShot.kind !== CAPABILITY_SUPPORT_KINDS.SUPPORTED) { throw new Error("CouchDB OneShot is unavailable"); } if (objectStorage.unattendedOneShot.kind !== CAPABILITY_SUPPORT_KINDS.SUPPORTED) { throw new Error("Object Storage OneShot is unavailable"); } await expect( couchDB.userInitiatedOneShot.run(couchInstance, setting, { trigger: "manual", progressPresentation: REPLICATION_PROGRESS_PRESENTATIONS.NOTICE, interaction: USER_INITIATED_REPLICATION_AUTHORITY, }) ).resolves.toBe(REPLICATION_COMPLETED); await expect( objectStorage.unattendedOneShot.run(objectInstance, setting, { trigger: "resume", interaction: NO_INTERACTION, }) ).resolves.toBe(REPLICATION_COMPLETED); expect(constructorMocks.couchDBOneShot).toHaveBeenCalledWith(setting, true); expect(constructorMocks.objectStorageOneShot).toHaveBeenCalledWith(setting, false); }); it.each([ ["CouchDB", REMOTE_COUCHDB], ["Object Storage", REMOTE_MINIO], ] as const)("maps %s progress presentation independently of recovery authority", async (label, remoteType) => { const definitions = createCentralReplicatorProviderDefinitions({} as never); const definition = definitions.get(remoteType)!; if (definition.userInitiatedOneShot.kind !== CAPABILITY_SUPPORT_KINDS.SUPPORTED) { throw new Error(`${label} OneShot is unavailable`); } const setting = Object.assign(createNewVaultSettings(), { remoteType }); const openOneShotReplicationWithOutcome = vi.fn(async () => REPLICATION_COMPLETED); const instance = { initializeDatabaseForReplication: vi.fn(async () => true), openReplication: vi.fn(async () => true), terminateSync: vi.fn(), closeReplication: vi.fn(), openOneShotReplicationWithOutcome, }; await definition.userInitiatedOneShot.run(instance, setting, { trigger: "manual", progressPresentation: REPLICATION_PROGRESS_PRESENTATIONS.QUIET, interaction: USER_INITIATED_REPLICATION_AUTHORITY, }); await definition.userInitiatedOneShot.run(instance, setting, { trigger: "manual", progressPresentation: REPLICATION_PROGRESS_PRESENTATIONS.NOTICE, interaction: USER_INITIATED_REPLICATION_AUTHORITY, }); expect(openOneShotReplicationWithOutcome).toHaveBeenNthCalledWith(1, setting, false); expect(openOneShotReplicationWithOutcome).toHaveBeenNthCalledWith(2, setting, true); }); it("dispatches central finite work through the declared operation rather than constructor identity", async () => { const definitions = createCentralReplicatorProviderDefinitions({} as never); const couchDB = definitions.get(REMOTE_COUCHDB)!; const objectStorage = definitions.get(REMOTE_MINIO)!; const setting = createNewVaultSettings(); const createStructuralOneShotReplicator = () => ({ initializeDatabaseForReplication: vi.fn(async () => true), openReplication: vi.fn(async () => true), terminateSync: vi.fn(), closeReplication: vi.fn(), openOneShotReplicationWithOutcome: vi.fn(async () => REPLICATION_COMPLETED), }); const couchInstance = createStructuralOneShotReplicator(); const objectStorageInstance = createStructuralOneShotReplicator(); if (couchDB.userInitiatedOneShot.kind !== CAPABILITY_SUPPORT_KINDS.SUPPORTED) { throw new Error("CouchDB OneShot is unavailable"); } if (objectStorage.unattendedOneShot.kind !== CAPABILITY_SUPPORT_KINDS.SUPPORTED) { throw new Error("Object Storage OneShot is unavailable"); } const couchOutcome = await couchDB.userInitiatedOneShot.run(couchInstance, setting, { trigger: "manual", progressPresentation: REPLICATION_PROGRESS_PRESENTATIONS.NOTICE, interaction: USER_INITIATED_REPLICATION_AUTHORITY, }); const objectStorageOutcome = await objectStorage.unattendedOneShot.run(objectStorageInstance, setting, { trigger: "resume", interaction: NO_INTERACTION, }); expect.soft(couchOutcome).toBe(REPLICATION_COMPLETED); expect.soft(objectStorageOutcome).toBe(REPLICATION_COMPLETED); expect(couchInstance.openOneShotReplicationWithOutcome).toHaveBeenCalledWith(setting, true); expect(objectStorageInstance.openOneShotReplicationWithOutcome).toHaveBeenCalledWith(setting, false); }); it("rejects a one-shot adapter whose Replicator does not declare the required operation", async () => { const definitions = createCentralReplicatorProviderDefinitions({} as never); const couchDB = definitions.get(REMOTE_COUCHDB)!; const setting = createNewVaultSettings(); const incompleteInstance = { initializeDatabaseForReplication: vi.fn(async () => true), openReplication: vi.fn(async () => true), terminateSync: vi.fn(), closeReplication: vi.fn(), }; if (couchDB.userInitiatedOneShot.kind !== CAPABILITY_SUPPORT_KINDS.SUPPORTED) { throw new Error("CouchDB OneShot is unavailable"); } const outcome = await couchDB.userInitiatedOneShot.run(incompleteInstance, setting, { trigger: "manual", progressPresentation: REPLICATION_PROGRESS_PRESENTATIONS.NOTICE, interaction: USER_INITIATED_REPLICATION_AUTHORITY, }); expect(outcome.status).toBe("failed"); expect(incompleteInstance.openReplication).not.toHaveBeenCalled(); }); });