mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-19 15:52:31 +00:00
test: recover review harness vault fixture
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
export type ReviewHarnessScenarioStatus =
|
||||||
|
| "idle"
|
||||||
|
| "queued"
|
||||||
|
| "running"
|
||||||
|
| "waiting-for-user"
|
||||||
|
| "passed"
|
||||||
|
| "failed"
|
||||||
|
| "cancelled";
|
||||||
|
|
||||||
|
export interface ReviewHarnessScenarioResult {
|
||||||
|
readonly status: ReviewHarnessScenarioStatus;
|
||||||
|
readonly detail: string;
|
||||||
|
readonly observations: readonly string[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import type { ReviewHarnessScenarioResult } from "./reviewHarnessTypes";
|
||||||
|
|
||||||
|
export const REVIEW_HARNESS_FIXTURE_ROOT = "__self-hosted-livesync-review-harness__";
|
||||||
|
export const REVIEW_HARNESS_SOURCE_FILE = `${REVIEW_HARNESS_FIXTURE_ROOT}/round-trip.md`;
|
||||||
|
export const REVIEW_HARNESS_RENAMED_FILE = `${REVIEW_HARNESS_FIXTURE_ROOT}/round-trip-renamed.md`;
|
||||||
|
|
||||||
|
const CREATED_CONTENT = "review-harness:created\n";
|
||||||
|
const MODIFIED_CONTENT = "review-harness:modified\n";
|
||||||
|
|
||||||
|
export interface ReviewHarnessVaultFixtureRuntime<TFile> {
|
||||||
|
confirmFixtureAccess(): Promise<boolean>;
|
||||||
|
fixtureRootExists(): boolean;
|
||||||
|
createFixtureRoot(): Promise<void>;
|
||||||
|
createFile(path: string, content: string): Promise<TFile>;
|
||||||
|
readFile(file: TFile): Promise<string>;
|
||||||
|
modifyFile(file: TFile, content: string): Promise<void>;
|
||||||
|
renameFile(file: TFile, path: string): Promise<void>;
|
||||||
|
filePath(file: TFile): string;
|
||||||
|
removeFixtureRoot(): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exercises a single, fixed fixture tree in a dedicated Vault.
|
||||||
|
*
|
||||||
|
* The runtime owns the platform adapter. This function owns the safety boundary:
|
||||||
|
* it refuses a pre-existing fixture root, tracks whether it created the root, and
|
||||||
|
* removes only that owned root in a `finally` block.
|
||||||
|
*/
|
||||||
|
export async function runReviewHarnessVaultRoundTrip<TFile>(
|
||||||
|
runtime: ReviewHarnessVaultFixtureRuntime<TFile>
|
||||||
|
): Promise<ReviewHarnessScenarioResult> {
|
||||||
|
if (!(await runtime.confirmFixtureAccess())) {
|
||||||
|
return {
|
||||||
|
status: "cancelled",
|
||||||
|
detail: "Vault fixture access was not approved.",
|
||||||
|
observations: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runtime.fixtureRootExists()) {
|
||||||
|
return {
|
||||||
|
status: "failed",
|
||||||
|
detail: "The owned fixture root already exists. It was left untouched.",
|
||||||
|
observations: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let fixtureOwned = false;
|
||||||
|
try {
|
||||||
|
await runtime.createFixtureRoot();
|
||||||
|
fixtureOwned = true;
|
||||||
|
const file = await runtime.createFile(REVIEW_HARNESS_SOURCE_FILE, CREATED_CONTENT);
|
||||||
|
const created = await runtime.readFile(file);
|
||||||
|
if (created !== CREATED_CONTENT) throw new Error("Created fixture content did not round-trip.");
|
||||||
|
|
||||||
|
await runtime.modifyFile(file, MODIFIED_CONTENT);
|
||||||
|
const modified = await runtime.readFile(file);
|
||||||
|
if (modified !== MODIFIED_CONTENT) throw new Error("Modified fixture content did not round-trip.");
|
||||||
|
|
||||||
|
await runtime.renameFile(file, REVIEW_HARNESS_RENAMED_FILE);
|
||||||
|
if (runtime.filePath(file) !== REVIEW_HARNESS_RENAMED_FILE) {
|
||||||
|
throw new Error("The fixture rename did not update its path.");
|
||||||
|
}
|
||||||
|
const renamed = await runtime.readFile(file);
|
||||||
|
if (renamed !== MODIFIED_CONTENT) throw new Error("Renamed fixture content was not preserved.");
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: "passed",
|
||||||
|
detail: "The owned fixture tree completed its round trip and was removed.",
|
||||||
|
observations: ["create", "read", "modify", "rename", "read-after-rename", "cleanup"],
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
if (fixtureOwned) await runtime.removeFixtureRoot();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import {
|
||||||
|
REVIEW_HARNESS_RENAMED_FILE,
|
||||||
|
REVIEW_HARNESS_SOURCE_FILE,
|
||||||
|
runReviewHarnessVaultRoundTrip,
|
||||||
|
type ReviewHarnessVaultFixtureRuntime,
|
||||||
|
} from "./reviewHarnessVaultFixture";
|
||||||
|
|
||||||
|
interface FixtureFile {
|
||||||
|
path: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRuntime(): ReviewHarnessVaultFixtureRuntime<FixtureFile> & {
|
||||||
|
events: string[];
|
||||||
|
rootExists: boolean;
|
||||||
|
} {
|
||||||
|
const runtime: ReviewHarnessVaultFixtureRuntime<FixtureFile> & {
|
||||||
|
events: string[];
|
||||||
|
rootExists: boolean;
|
||||||
|
} = {
|
||||||
|
events: [],
|
||||||
|
rootExists: false,
|
||||||
|
confirmFixtureAccess: vi.fn(async () => true),
|
||||||
|
fixtureRootExists() {
|
||||||
|
return this.rootExists;
|
||||||
|
},
|
||||||
|
async createFixtureRoot() {
|
||||||
|
this.events.push("create-root");
|
||||||
|
this.rootExists = true;
|
||||||
|
},
|
||||||
|
async createFile(path, content) {
|
||||||
|
this.events.push(`create-file:${path}`);
|
||||||
|
return { path, content };
|
||||||
|
},
|
||||||
|
async readFile(file) {
|
||||||
|
this.events.push(`read:${file.path}`);
|
||||||
|
return file.content;
|
||||||
|
},
|
||||||
|
async modifyFile(file, content) {
|
||||||
|
this.events.push(`modify:${file.path}`);
|
||||||
|
file.content = content;
|
||||||
|
},
|
||||||
|
async renameFile(file, path) {
|
||||||
|
this.events.push(`rename:${file.path}->${path}`);
|
||||||
|
file.path = path;
|
||||||
|
},
|
||||||
|
filePath: (file) => file.path,
|
||||||
|
async removeFixtureRoot() {
|
||||||
|
this.events.push("remove-root");
|
||||||
|
this.rootExists = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("runReviewHarnessVaultRoundTrip", () => {
|
||||||
|
it("does not inspect or mutate the Vault when access is declined", async () => {
|
||||||
|
const runtime = createRuntime();
|
||||||
|
vi.mocked(runtime.confirmFixtureAccess).mockResolvedValue(false);
|
||||||
|
const exists = vi.spyOn(runtime, "fixtureRootExists");
|
||||||
|
|
||||||
|
await expect(runReviewHarnessVaultRoundTrip(runtime)).resolves.toMatchObject({ status: "cancelled" });
|
||||||
|
|
||||||
|
expect(exists).not.toHaveBeenCalled();
|
||||||
|
expect(runtime.events).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("leaves a pre-existing fixture root untouched", async () => {
|
||||||
|
const runtime = createRuntime();
|
||||||
|
runtime.rootExists = true;
|
||||||
|
|
||||||
|
await expect(runReviewHarnessVaultRoundTrip(runtime)).resolves.toMatchObject({ status: "failed" });
|
||||||
|
|
||||||
|
expect(runtime.events).toEqual([]);
|
||||||
|
expect(runtime.rootExists).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("completes the round trip and removes its owned fixture root", async () => {
|
||||||
|
const runtime = createRuntime();
|
||||||
|
|
||||||
|
await expect(runReviewHarnessVaultRoundTrip(runtime)).resolves.toMatchObject({ status: "passed" });
|
||||||
|
|
||||||
|
expect(runtime.events).toEqual([
|
||||||
|
"create-root",
|
||||||
|
`create-file:${REVIEW_HARNESS_SOURCE_FILE}`,
|
||||||
|
`read:${REVIEW_HARNESS_SOURCE_FILE}`,
|
||||||
|
`modify:${REVIEW_HARNESS_SOURCE_FILE}`,
|
||||||
|
`read:${REVIEW_HARNESS_SOURCE_FILE}`,
|
||||||
|
`rename:${REVIEW_HARNESS_SOURCE_FILE}->${REVIEW_HARNESS_RENAMED_FILE}`,
|
||||||
|
`read:${REVIEW_HARNESS_RENAMED_FILE}`,
|
||||||
|
"remove-root",
|
||||||
|
]);
|
||||||
|
expect(runtime.rootExists).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes an owned fixture root when an operation fails", async () => {
|
||||||
|
const runtime = createRuntime();
|
||||||
|
runtime.modifyFile = vi.fn(async () => {
|
||||||
|
throw new Error("fixture write failed");
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(runReviewHarnessVaultRoundTrip(runtime)).rejects.toThrow("fixture write failed");
|
||||||
|
|
||||||
|
expect(runtime.events[runtime.events.length - 1]).toBe("remove-root");
|
||||||
|
expect(runtime.rootExists).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not remove a root when creating it fails", async () => {
|
||||||
|
const runtime = createRuntime();
|
||||||
|
runtime.createFixtureRoot = vi.fn(async () => {
|
||||||
|
throw new Error("root creation failed");
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(runReviewHarnessVaultRoundTrip(runtime)).rejects.toThrow("root creation failed");
|
||||||
|
|
||||||
|
expect(runtime.events).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user