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

195 lines
7.0 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { InjectableReplicationService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableReplicationService";
import { ServiceContext } from "@vrtmrz/livesync-commonlib/compat/services/base/ServiceBase";
import { evaluateCompatibilityPause, legacyDatabaseCompatibilityVersionKey } from "./databaseCompatibility.ts";
function migrationState(overrides: Record<string, unknown> = {}) {
return {
sourceVersion: 2,
targetVersion: 2,
isNewVault: false,
isFromFutureSchema: false,
changed: false,
requiresSyncReview: false,
reviewReasons: [],
...overrides,
} as never;
}
describe("database compatibility evaluation", () => {
it("initialises a new Vault without presenting an upgrade review", () => {
const result = evaluateCompatibilityPause({
acknowledgedVersion: null,
currentVersion: 12,
migrationState: migrationState({ isNewVault: true }),
legacyReviewMessage: "",
});
expect(result).toEqual({ initialiseAcknowledgedVersion: true });
});
it("allows an older acknowledged version to be reviewed and resumed", () => {
const result = evaluateCompatibilityPause({
acknowledgedVersion: "11",
currentVersion: 12,
migrationState: migrationState(),
legacyReviewMessage: "",
});
expect(result.pause).toEqual({
resumable: true,
reasons: [
{
source: "database-version",
state: "upgrade",
acknowledgedVersion: 11,
currentVersion: 12,
resumable: true,
},
],
});
expect(result.initialiseAcknowledgedVersion).toBe(false);
});
it("does not permit an older implementation to acknowledge a newer database version", () => {
const result = evaluateCompatibilityPause({
acknowledgedVersion: "13",
currentVersion: 12,
migrationState: migrationState(),
legacyReviewMessage: "",
});
expect(result.pause?.resumable).toBe(false);
expect(result.pause?.reasons[0]).toMatchObject({
source: "database-version",
state: "downgrade",
acknowledgedVersion: 13,
currentVersion: 12,
});
});
it("requires review when an existing Vault has no valid acknowledged version", () => {
for (const acknowledgedVersion of [null, "invalid"]) {
const result = evaluateCompatibilityPause({
acknowledgedVersion,
currentVersion: 12,
migrationState: migrationState(),
legacyReviewMessage: "",
});
expect(result.pause?.resumable).toBe(true);
expect(result.pause?.reasons[0]).toMatchObject({ source: "database-version" });
}
});
it("does not permit a future settings schema to be acknowledged by an older implementation", () => {
const result = evaluateCompatibilityPause({
acknowledgedVersion: "12",
currentVersion: 12,
migrationState: migrationState({
sourceVersion: 3,
targetVersion: 2,
isFromFutureSchema: true,
requiresSyncReview: true,
}),
legacyReviewMessage: "",
});
expect(result.pause).toEqual({
resumable: false,
reasons: [
{
source: "settings-schema",
sourceVersion: 3,
currentVersion: 2,
isFromFutureSchema: true,
resumable: false,
reviewReasons: [],
},
],
});
});
it("retains a settings migration review in the host compatibility reason", () => {
const reviewReasons = [
{
code: "legacy-update-review-pending",
fromVersion: 9,
toVersion: 10,
},
];
const result = evaluateCompatibilityPause({
acknowledgedVersion: "12",
currentVersion: 12,
migrationState: migrationState({
sourceVersion: 9,
targetVersion: 10,
requiresSyncReview: true,
reviewReasons,
}),
legacyReviewMessage: "",
});
expect(result.pause?.reasons).toContainEqual({
source: "settings-schema",
sourceVersion: 9,
currentVersion: 10,
isFromFutureSchema: false,
resumable: true,
reviewReasons,
});
});
it("compatibility: retains an earlier unstructured review when no structured reason can be reconstructed", () => {
const result = evaluateCompatibilityPause({
acknowledgedVersion: "12",
currentVersion: 12,
migrationState: migrationState(),
legacyReviewMessage: "Review an earlier compatibility change.",
});
expect(result.pause).toEqual({
resumable: true,
reasons: [
{
source: "legacy-review",
message: "Review an earlier compatibility change.",
resumable: true,
},
],
});
});
it("compatibility: scopes the earlier review marker to the Vault", () => {
expect(legacyDatabaseCompatibilityVersionKey("Example Vault")).toBe("obsidian-live-sync-verExample Vault");
});
});
describe("packaged Commonlib compatibility gate", () => {
it("prevents replication while the compatibility review remains pending", async () => {
const openReplication = vi.fn().mockResolvedValue(true);
const runFiniteReplicationActivity = vi.fn(async (task: () => unknown) => await task());
const dependencies = {
APIService: { isOnline: true, addLog: vi.fn() },
appLifecycleService: {
isReady: () => true,
getUnresolvedMessages: Object.assign(vi.fn().mockResolvedValue([]), { addHandler: vi.fn() }),
},
databaseService: {},
fileProcessingService: { commitPendingFileEvents: vi.fn().mockResolvedValue(true) },
replicatorService: {
getActiveReplicator: () => ({ openReplication }),
runFiniteReplicationActivity,
},
settingService: {
currentSettings: () => ({ versionUpFlash: "Review the database compatibility change." }),
},
};
const service = new InjectableReplicationService(new ServiceContext(), dependencies as never);
await expect(service.replicate(true)).resolves.toBe(false);
expect(runFiniteReplicationActivity).not.toHaveBeenCalled();
expect(openReplication).not.toHaveBeenCalled();
});
});