Files
obsidian-livesync/src/serviceFeatures/metadataIdentityRepair.unit.spec.ts
T

109 lines
4.1 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import type { DocumentID } from "@vrtmrz/livesync-commonlib/compat/common/types";
import type {
MetadataDocumentRepairRequest,
MetadataDocumentRepairResult,
} from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/offlineScanner";
import { executeMetadataIdentityRepair } from "./metadataIdentityRepair";
const request: MetadataDocumentRepairRequest = {
actualDocumentId: "f:stale" as DocumentID,
expectedDocumentId: "f:expected" as DocumentID,
sourceRevision: "4-source",
};
function createDependencies() {
const events: string[] = [];
return {
events,
confirm: vi.fn(async () => true),
repair: vi.fn(async (): Promise<MetadataDocumentRepairResult> => {
events.push("repair");
return {
status: "completed" as const,
...request,
targetCreated: true,
};
}),
requestOrdinaryScan: vi.fn(async () => {
events.push("scan");
return true;
}),
};
}
describe("executeMetadataIdentityRepair", () => {
it("performs no mutation when the separate confirmation is cancelled", async () => {
const dependencies = createDependencies();
dependencies.confirm.mockResolvedValue(false);
await expect(executeMetadataIdentityRepair(request, dependencies)).resolves.toEqual({
status: "cancelled",
});
expect(dependencies.repair).not.toHaveBeenCalled();
expect(dependencies.requestOrdinaryScan).not.toHaveBeenCalled();
});
it("requests an ordinary scan only after Commonlib completes the exact repair", async () => {
const dependencies = createDependencies();
await expect(executeMetadataIdentityRepair(request, dependencies)).resolves.toMatchObject({
status: "repair-result",
result: { status: "completed" },
scanCompleted: true,
});
expect(dependencies.repair).toHaveBeenCalledWith(request);
expect(dependencies.requestOrdinaryScan).toHaveBeenCalledOnce();
expect(dependencies.repair.mock.invocationCallOrder[0]).toBeLessThan(
dependencies.requestOrdinaryScan.mock.invocationCallOrder[0]
);
expect(dependencies.events).toEqual(["repair", "scan"]);
});
it("keeps a completed repair distinct when the ordinary scan cannot start", async () => {
const dependencies = createDependencies();
dependencies.requestOrdinaryScan.mockResolvedValue(false);
await expect(executeMetadataIdentityRepair(request, dependencies)).resolves.toMatchObject({
status: "repair-result",
result: { status: "completed" },
scanCompleted: false,
});
expect(dependencies.requestOrdinaryScan).toHaveBeenCalledOnce();
expect(dependencies.events).toEqual(["repair"]);
});
it("keeps a completed repair distinct when requesting the ordinary scan throws", async () => {
const dependencies = createDependencies();
const error = new Error("scan unavailable");
dependencies.requestOrdinaryScan.mockRejectedValue(error);
await expect(executeMetadataIdentityRepair(request, dependencies)).resolves.toMatchObject({
status: "repair-result",
result: { status: "completed" },
scanCompleted: false,
scanError: error,
});
expect(dependencies.events).toEqual(["repair"]);
});
it("does not scan after a stale, blocked, or failed repair result", async () => {
for (const status of ["stale", "blocked", "failed"] as const) {
const dependencies = createDependencies();
dependencies.repair.mockResolvedValue({
status,
...request,
targetCreated: false,
});
await expect(executeMetadataIdentityRepair(request, dependencies)).resolves.toMatchObject({
status: "repair-result",
result: { status },
scanCompleted: false,
});
expect(dependencies.requestOrdinaryScan).not.toHaveBeenCalled();
}
});
});