mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-07-23 04:52:58 +00:00
269 lines
10 KiB
TypeScript
269 lines
10 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import { NEW_VAULT_SETTINGS, type SettingsMigrationState } from "@vrtmrz/livesync-commonlib/settings";
|
|
import type { CompatibilityPause } from "@/common/databaseCompatibility.ts";
|
|
import { ReviewHarnessController, type ReviewHarnessRuntime } from "./reviewHarnessController";
|
|
|
|
function migration(): SettingsMigrationState {
|
|
return {
|
|
sourceVersion: 9,
|
|
targetVersion: 10,
|
|
isNewVault: false,
|
|
isFromFutureSchema: false,
|
|
changed: true,
|
|
requiresSyncReview: true,
|
|
reviewReasons: [
|
|
{
|
|
code: "legacy-update-review-pending",
|
|
fromVersion: 9,
|
|
toVersion: 10,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
function compatibilityPause(): CompatibilityPause {
|
|
return {
|
|
resumable: true,
|
|
reasons: [
|
|
{
|
|
source: "settings-schema",
|
|
sourceVersion: 9,
|
|
currentVersion: 10,
|
|
isFromFutureSchema: false,
|
|
resumable: true,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
function createRuntime(): ReviewHarnessRuntime & {
|
|
compatibilityReviewInitialised: boolean;
|
|
compatibilityPause: CompatibilityPause | undefined;
|
|
continuation: string | null;
|
|
events: string[];
|
|
} {
|
|
const services = {};
|
|
const replicator = { env: { services } };
|
|
const runtime: ReviewHarnessRuntime & {
|
|
compatibilityReviewInitialised: boolean;
|
|
compatibilityPause: CompatibilityPause | undefined;
|
|
continuation: string | null;
|
|
events: string[];
|
|
} = {
|
|
compatibilityReviewInitialised: true,
|
|
compatibilityPause: compatibilityPause(),
|
|
continuation: null,
|
|
events: [],
|
|
now: () => new Date("2026-07-18T12:00:00.000Z"),
|
|
getSettings: () => ({
|
|
liveSync: true,
|
|
syncOnSave: false,
|
|
syncOnEditorSave: true,
|
|
syncOnStart: false,
|
|
syncOnFileOpen: true,
|
|
syncAfterMerge: false,
|
|
periodicReplication: true,
|
|
}),
|
|
getNewVaultSettings: () => NEW_VAULT_SETTINGS,
|
|
getSettingsMigrationState: () => migration(),
|
|
isCompatibilityReviewInitialised() {
|
|
return this.compatibilityReviewInitialised;
|
|
},
|
|
getCompatibilityPause() {
|
|
return this.compatibilityPause;
|
|
},
|
|
openCompatibilityReview: vi.fn(async () => {
|
|
runtime.events.push("open-compatibility-review");
|
|
runtime.compatibilityPause = undefined;
|
|
}),
|
|
getP2PComposition: () => ({ first: replicator, second: replicator, expectedServices: services }),
|
|
runVaultRoundTrip: vi.fn(async () => ({
|
|
status: "passed" as const,
|
|
detail: "The owned fixture tree was exercised and removed.",
|
|
observations: [],
|
|
})),
|
|
readContinuation() {
|
|
return this.continuation;
|
|
},
|
|
writeContinuation(value) {
|
|
this.events.push("write");
|
|
this.continuation = value;
|
|
},
|
|
deleteContinuation() {
|
|
this.events.push("delete");
|
|
this.continuation = null;
|
|
},
|
|
restart: vi.fn(),
|
|
reportError: vi.fn(),
|
|
copyText: vi.fn(async () => undefined),
|
|
getEnvironment: () => ({
|
|
pluginVersion: "1.0.0-beta.1",
|
|
obsidianVersion: "1.13.1",
|
|
platform: "desktop",
|
|
userAgent: "test",
|
|
viewport: "1280x720",
|
|
}),
|
|
};
|
|
runtime.restart = vi.fn(() => runtime.events.push("restart"));
|
|
return runtime;
|
|
}
|
|
|
|
describe("ReviewHarnessController", () => {
|
|
it("runs the automatic settings and P2P composition checks", async () => {
|
|
const runtime = createRuntime();
|
|
const controller = new ReviewHarnessController(runtime);
|
|
|
|
await controller.runAutomaticScenarios();
|
|
|
|
expect(controller.snapshot().results["settings-lifecycle"].status).toBe("passed");
|
|
expect(controller.snapshot().results["p2p-composition"].status).toBe("passed");
|
|
expect(controller.snapshot().results["vault-round-trip"].status).toBe("idle");
|
|
expect(controller.snapshot().results["compatibility-review"].status).toBe("idle");
|
|
});
|
|
|
|
it("runs the Vault fixture scenario only when it is selected explicitly", async () => {
|
|
const runtime = createRuntime();
|
|
const controller = new ReviewHarnessController(runtime);
|
|
|
|
await controller.runAutomaticScenarios();
|
|
expect(runtime.runVaultRoundTrip).not.toHaveBeenCalled();
|
|
|
|
await controller.runScenario("vault-round-trip");
|
|
expect(runtime.runVaultRoundTrip).toHaveBeenCalledOnce();
|
|
expect(controller.snapshot().results["vault-round-trip"].status).toBe("passed");
|
|
});
|
|
|
|
it("does not copy unexpected runtime error details into the report", async () => {
|
|
const runtime = createRuntime();
|
|
runtime.runVaultRoundTrip = vi.fn(async () => {
|
|
throw new Error("Failed below /Users/reviewer/private-vault/secret-note.md");
|
|
});
|
|
const controller = new ReviewHarnessController(runtime);
|
|
|
|
await controller.runScenario("vault-round-trip");
|
|
|
|
const result = controller.snapshot().results["vault-round-trip"];
|
|
const report = controller.createReport();
|
|
expect(result).toMatchObject({
|
|
status: "failed",
|
|
detail: "The scenario failed unexpectedly. Review the in-app logs for diagnostic details.",
|
|
});
|
|
expect(report).not.toContain("private-vault");
|
|
expect(report).not.toContain("secret-note.md");
|
|
expect(runtime.reportError).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("deletes a one-shot continuation before exposing the resumed guided step", () => {
|
|
const runtime = createRuntime();
|
|
runtime.continuation = JSON.stringify({
|
|
formatVersion: 1,
|
|
requestId: "compatibility-review-2026-07-18T11:59:00.000Z",
|
|
scenarioId: "compatibility-review",
|
|
stage: "awaiting-restart",
|
|
requestedAt: "2026-07-18T11:59:00.000Z",
|
|
});
|
|
const controller = new ReviewHarnessController(runtime);
|
|
|
|
controller.consumeContinuation();
|
|
|
|
expect(runtime.events).toEqual(["delete"]);
|
|
expect(controller.snapshot().results["compatibility-review"]).toMatchObject({
|
|
status: "waiting-for-user",
|
|
});
|
|
expect(controller.snapshot().resumedRequestId).toBe(
|
|
"compatibility-review-2026-07-18T11:59:00.000Z"
|
|
);
|
|
});
|
|
|
|
it("does not copy rejected continuation values into the report", () => {
|
|
const runtime = createRuntime();
|
|
runtime.continuation = JSON.stringify({
|
|
formatVersion: 1,
|
|
requestId: "review-1",
|
|
scenarioId: "/Users/reviewer/private-vault/secret-note.md",
|
|
stage: "awaiting-restart",
|
|
requestedAt: "2026-07-18T11:59:00.000Z",
|
|
});
|
|
const controller = new ReviewHarnessController(runtime);
|
|
|
|
controller.consumeContinuation();
|
|
|
|
expect(controller.snapshot().continuationError).toContain("private-vault");
|
|
expect(controller.snapshot().results["compatibility-review"].detail).toBe(
|
|
"The stored continuation was invalid and was removed."
|
|
);
|
|
expect(controller.createReport()).not.toContain("private-vault");
|
|
expect(controller.createReport()).not.toContain("secret-note.md");
|
|
});
|
|
|
|
it("does not accept an injected request ID which could enter the report transcript", () => {
|
|
const runtime = createRuntime();
|
|
runtime.continuation = JSON.stringify({
|
|
formatVersion: 1,
|
|
requestId: "/Users/reviewer/private-vault/secret-note.md",
|
|
scenarioId: "compatibility-review",
|
|
stage: "awaiting-restart",
|
|
requestedAt: "2026-07-18T11:59:00.000Z",
|
|
});
|
|
const controller = new ReviewHarnessController(runtime);
|
|
|
|
controller.consumeContinuation();
|
|
|
|
expect(controller.snapshot().results["compatibility-review"].status).toBe("failed");
|
|
expect(controller.createReport()).not.toContain("private-vault");
|
|
expect(controller.createReport()).not.toContain("secret-note.md");
|
|
});
|
|
|
|
it("persists the fixed continuation before requesting restart", () => {
|
|
const runtime = createRuntime();
|
|
const controller = new ReviewHarnessController(runtime);
|
|
|
|
controller.prepareCompatibilityReviewRestart();
|
|
|
|
expect(runtime.events).toEqual(["write", "restart"]);
|
|
expect(JSON.parse(runtime.continuation!)).toMatchObject({
|
|
formatVersion: 1,
|
|
scenarioId: "compatibility-review",
|
|
stage: "awaiting-restart",
|
|
});
|
|
});
|
|
|
|
it("passes only after the actual compatibility review clears its pause", async () => {
|
|
const runtime = createRuntime();
|
|
const controller = new ReviewHarnessController(runtime);
|
|
await controller.runScenario("compatibility-review");
|
|
|
|
await controller.openCompatibilityReview();
|
|
|
|
expect(runtime.openCompatibilityReview).toHaveBeenCalledOnce();
|
|
expect(controller.snapshot().results["compatibility-review"]).toMatchObject({
|
|
status: "passed",
|
|
detail: "The device-local compatibility pause was reviewed and cleared.",
|
|
});
|
|
});
|
|
|
|
it("remains waiting when the actual compatibility review stays paused", async () => {
|
|
const runtime = createRuntime();
|
|
runtime.openCompatibilityReview = vi.fn(async () => undefined);
|
|
const controller = new ReviewHarnessController(runtime);
|
|
await controller.runScenario("compatibility-review");
|
|
|
|
await controller.openCompatibilityReview();
|
|
|
|
expect(controller.snapshot().results["compatibility-review"].status).toBe("waiting-for-user");
|
|
});
|
|
|
|
it("copies a Markdown report after compatibility evidence is recorded", async () => {
|
|
const runtime = createRuntime();
|
|
const controller = new ReviewHarnessController(runtime);
|
|
await controller.runScenario("compatibility-review");
|
|
await controller.openCompatibilityReview();
|
|
|
|
await controller.copyReport();
|
|
|
|
expect(runtime.copyText).toHaveBeenCalledOnce();
|
|
expect(vi.mocked(runtime.copyText).mock.calls[0][0]).toContain("Compatibility review boundary");
|
|
expect(controller.snapshot().results["compatibility-review"].status).toBe("passed");
|
|
});
|
|
});
|