Files
obsidian-livesync/src/common/replicatorConfigurationIdentity.unit.spec.ts
T

175 lines
7.5 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
import { createNewVaultSettings } from "@vrtmrz/livesync-commonlib/settings";
import {
getCouchDBReplicatorConfigurationIdentity,
getObjectStorageReplicatorConfigurationIdentity,
} from "./replicatorConfigurationIdentity";
describe("active Replicator configuration identity", () => {
function configuredSettings(overrides: Partial<ObsidianLiveSyncSettings> = {}): ObsidianLiveSyncSettings {
return Object.assign(createNewVaultSettings(), {
activeConfigurationId: "profile-a",
couchDB_URI: "https://couch.example.test/base",
couchDB_USER: "alice",
couchDB_PASSWORD: "secret-a",
couchDB_DBNAME: "vault",
couchDB_CustomHeaders: "X-Second: two\nX-First: one",
endpoint: "https://objects.example.test/base",
accessKey: "alice",
secretKey: "secret-a",
bucket: "vault",
bucketPrefix: "notes/",
region: "auto",
bucketCustomHeaders: "X-Second: two\nX-First: one",
encrypt: true,
passphrase: "encryption-a",
useDynamicIterationCount: false,
permitEmptyPassphrase: false,
enableCompression: false,
...overrides,
});
}
it.each([
["couchDB_URI", "https://other.example.test/base"],
["couchDB_DBNAME", "other-vault"],
["couchDB_USER", "bob"],
["couchDB_PASSWORD", "secret-b"],
["couchDB_CustomHeaders", "X-First: changed"],
["useRequestAPI", true],
["disableRequestURI", true],
["encrypt", false],
["passphrase", "encryption-b"],
["useDynamicIterationCount", true],
["E2EEAlgorithm", ""],
["permitEmptyPassphrase", true],
["enableCompression", true],
] satisfies Array<[keyof ObsidianLiveSyncSettings, ObsidianLiveSyncSettings[keyof ObsidianLiveSyncSettings]]>)(
"detects a CouchDB %s change",
(key, value) => {
const settings = configuredSettings();
expect(getCouchDBReplicatorConfigurationIdentity({ ...settings, [key]: value })).not.toBe(
getCouchDBReplicatorConfigurationIdentity(settings)
);
}
);
it("ignores persisted central profile identity when the effective connection settings match", () => {
const settings = configuredSettings({ activeConfigurationId: "profile-a" });
const otherProfile = { ...settings, activeConfigurationId: "profile-b" };
expect(getCouchDBReplicatorConfigurationIdentity(otherProfile)).toBe(
getCouchDBReplicatorConfigurationIdentity(settings)
);
expect(getObjectStorageReplicatorConfigurationIdentity(otherProfile)).toBe(
getObjectStorageReplicatorConfigurationIdentity(settings)
);
});
it("projects only the active CouchDB authentication mode", () => {
const basic = configuredSettings({ useJWT: false, jwtKey: "inactive-a" });
expect(getCouchDBReplicatorConfigurationIdentity({ ...basic, jwtKey: "inactive-b" })).toBe(
getCouchDBReplicatorConfigurationIdentity(basic)
);
const jwt = configuredSettings({
useJWT: true,
jwtAlgorithm: "HS256",
jwtKey: "jwt-a",
jwtKid: "kid-a",
jwtSub: "subject-a",
jwtExpDuration: 5,
});
expect(getCouchDBReplicatorConfigurationIdentity({ ...jwt, couchDB_PASSWORD: "inactive" })).toBe(
getCouchDBReplicatorConfigurationIdentity(jwt)
);
expect(getCouchDBReplicatorConfigurationIdentity({ ...jwt, jwtKey: "jwt-b" })).not.toBe(
getCouchDBReplicatorConfigurationIdentity(jwt)
);
});
it.each([
["endpoint", "https://other.example.test/base"],
["bucket", "other-vault"],
["bucketPrefix", "archive/"],
["region", "eu-west-1"],
["accessKey", "bob"],
["secretKey", "secret-b"],
["forcePathStyle", false],
["useCustomRequestHandler", true],
["bucketCustomHeaders", "X-First: changed"],
["encrypt", false],
["passphrase", "encryption-b"],
["useDynamicIterationCount", true],
["E2EEAlgorithm", ""],
["permitEmptyPassphrase", true],
] satisfies Array<[keyof ObsidianLiveSyncSettings, ObsidianLiveSyncSettings[keyof ObsidianLiveSyncSettings]]>)(
"detects an Object Storage %s change",
(key, value) => {
const settings = configuredSettings();
expect(getObjectStorageReplicatorConfigurationIdentity({ ...settings, [key]: value })).not.toBe(
getObjectStorageReplicatorConfigurationIdentity(settings)
);
}
);
it("normalises endpoint and header representation without using the setup URI grammar", () => {
const settings = configuredSettings();
const couchIdentity = getCouchDBReplicatorConfigurationIdentity(settings);
const objectStorageIdentity = getObjectStorageReplicatorConfigurationIdentity(settings);
expect(
getCouchDBReplicatorConfigurationIdentity({
...settings,
couchDB_URI: "https://couch.example.test:443/base/",
couchDB_CustomHeaders: "X-First: one\nX-Second: two",
})
).toBe(couchIdentity);
expect(
getObjectStorageReplicatorConfigurationIdentity({
...settings,
endpoint: "https://objects.example.test:443/base/",
bucketCustomHeaders: "X-First: one\nX-Second: two",
})
).toBe(objectStorageIdentity);
});
it("ignores inactive remote-security credentials", () => {
const settings = configuredSettings({ encrypt: false, passphrase: "inactive-a" });
expect(
getCouchDBReplicatorConfigurationIdentity({
...settings,
passphrase: "inactive-b",
useDynamicIterationCount: !settings.useDynamicIterationCount,
E2EEAlgorithm: "",
permitEmptyPassphrase: !settings.permitEmptyPassphrase,
})
).toBe(getCouchDBReplicatorConfigurationIdentity(settings));
expect(
getObjectStorageReplicatorConfigurationIdentity({
...settings,
passphrase: "inactive-b",
useDynamicIterationCount: !settings.useDynamicIterationCount,
E2EEAlgorithm: "",
permitEmptyPassphrase: !settings.permitEmptyPassphrase,
})
).toBe(getObjectStorageReplicatorConfigurationIdentity(settings));
});
it("keeps malformed endpoints deterministic and scoped", () => {
const settings = configuredSettings({ couchDB_URI: "not a URL", endpoint: "also not a URL" });
expect(() => getCouchDBReplicatorConfigurationIdentity(settings)).not.toThrow();
expect(() => getObjectStorageReplicatorConfigurationIdentity(settings)).not.toThrow();
expect(
getCouchDBReplicatorConfigurationIdentity({ ...settings, couchDB_URI: "different invalid URL" })
).not.toBe(getCouchDBReplicatorConfigurationIdentity(settings));
const unrelatedPluginChange = { ...settings, displayLanguage: "ja" };
expect(getObjectStorageReplicatorConfigurationIdentity(unrelatedPluginChange)).toBe(
getObjectStorageReplicatorConfigurationIdentity(settings)
);
});
});