mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-09-10 12:47:05 +00:00
231 lines
7.7 KiB
TypeScript
231 lines
7.7 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { FilePathWithPrefix } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
|
import type { FileRepairInspection, FileRepairRevision } from "./fileRepair";
|
|
import {
|
|
getFileRepairRevisionActions,
|
|
getFileRepairRevisionComparison,
|
|
} from "./fileRepairPresentation";
|
|
|
|
function createInspection(
|
|
revision: Partial<FileRepairRevision> = {},
|
|
storage: { exists: boolean; size?: number; mtime?: number } = {
|
|
exists: true,
|
|
size: 12,
|
|
mtime: 5_500,
|
|
}
|
|
): { inspection: FileRepairInspection; revision: FileRepairRevision } {
|
|
const completeRevision = {
|
|
role: "conflict",
|
|
metadata: {
|
|
documentId: "f:note",
|
|
revision: "2-conflict",
|
|
current: false,
|
|
deleted: false,
|
|
storageType: "plain",
|
|
storageLayout: "chunked",
|
|
ctime: 1,
|
|
mtime: 2_000,
|
|
recordedSize: 9,
|
|
revisionHistory: [],
|
|
chunkReferences: 0,
|
|
uniqueChunkReferences: 0,
|
|
embeddedChunkReferences: 0,
|
|
locallyStoredChunkReferences: 0,
|
|
contentAvailableLocally: true,
|
|
chunks: [],
|
|
},
|
|
contentReadable: true,
|
|
contentMatchesStorage: false,
|
|
loadedEntry: {
|
|
_id: "f:note",
|
|
_rev: "2-conflict",
|
|
path: "note.md",
|
|
ctime: 1,
|
|
mtime: 2_000,
|
|
size: 9,
|
|
type: "plain",
|
|
datatype: "plain",
|
|
children: [],
|
|
eden: {},
|
|
data: "content",
|
|
},
|
|
...revision,
|
|
} as FileRepairRevision;
|
|
const inspection = {
|
|
information: {
|
|
path: "note.md",
|
|
databasePath: "note.md" as FilePathWithPrefix,
|
|
storage,
|
|
database: {
|
|
source: "local database on this device",
|
|
remoteQueried: false,
|
|
exists: true,
|
|
currentRevision: "3-winner",
|
|
conflictCount: 1,
|
|
conflictRevisions: ["2-conflict"],
|
|
unavailableConflictRevisions: [],
|
|
revisions: [],
|
|
mergeBases: [],
|
|
},
|
|
},
|
|
revisions: [completeRevision],
|
|
requiresAttention: true,
|
|
} satisfies FileRepairInspection;
|
|
return { inspection, revision: completeRevision };
|
|
}
|
|
|
|
describe("file repair presentation", () => {
|
|
it("offers both reconciliation directions for a readable differing revision", () => {
|
|
const { inspection, revision } = createInspection();
|
|
|
|
expect(getFileRepairRevisionActions(inspection, revision)).toEqual({
|
|
compareWithVault: true,
|
|
applyRevisionToVault: true,
|
|
markAsVaultRevision: false,
|
|
storeVaultOnBranch: true,
|
|
applyLogicalDeletionToVault: false,
|
|
retryRevision: false,
|
|
discardRevision: false,
|
|
discardBranch: true,
|
|
});
|
|
});
|
|
|
|
it("marks an exact matching revision without creating another child", () => {
|
|
const { inspection, revision } = createInspection({
|
|
contentMatchesStorage: true,
|
|
});
|
|
|
|
expect(getFileRepairRevisionActions(inspection, revision)).toMatchObject({
|
|
compareWithVault: false,
|
|
applyRevisionToVault: false,
|
|
markAsVaultRevision: true,
|
|
storeVaultOnBranch: false,
|
|
discardBranch: true,
|
|
});
|
|
});
|
|
|
|
it("does not offer a text comparison for a binary file", () => {
|
|
const { inspection, revision } = createInspection();
|
|
inspection.information.path = "image.png";
|
|
|
|
expect(getFileRepairRevisionActions(inspection, revision)).toMatchObject({
|
|
compareWithVault: false,
|
|
applyRevisionToVault: true,
|
|
storeVaultOnBranch: true,
|
|
});
|
|
});
|
|
|
|
it("offers explicit deletion or branch extension for a logical deletion", () => {
|
|
const { inspection, revision } = createInspection({
|
|
metadata: {
|
|
...createInspection().revision.metadata,
|
|
deleted: true,
|
|
},
|
|
contentReadable: true,
|
|
contentMatchesStorage: null,
|
|
loadedEntry: false,
|
|
});
|
|
|
|
expect(getFileRepairRevisionActions(inspection, revision)).toMatchObject({
|
|
applyRevisionToVault: false,
|
|
storeVaultOnBranch: true,
|
|
applyLogicalDeletionToVault: true,
|
|
retryRevision: false,
|
|
discardRevision: false,
|
|
discardBranch: true,
|
|
});
|
|
});
|
|
|
|
it("offers retry, discard, and branch extension for an unreadable live revision", () => {
|
|
const { inspection, revision } = createInspection({
|
|
contentReadable: false,
|
|
contentMatchesStorage: null,
|
|
loadedEntry: false,
|
|
});
|
|
|
|
expect(getFileRepairRevisionActions(inspection, revision)).toMatchObject({
|
|
compareWithVault: false,
|
|
applyRevisionToVault: false,
|
|
markAsVaultRevision: false,
|
|
storeVaultOnBranch: true,
|
|
retryRevision: true,
|
|
discardRevision: false,
|
|
discardBranch: true,
|
|
});
|
|
});
|
|
|
|
it("keeps the existing unreadable-leaf escape hatch when there is no conflict branch", () => {
|
|
const { inspection, revision } = createInspection({
|
|
role: "winner",
|
|
contentReadable: false,
|
|
contentMatchesStorage: null,
|
|
loadedEntry: false,
|
|
});
|
|
inspection.information.database.conflictCount = 0;
|
|
inspection.information.database.conflictRevisions = [];
|
|
inspection.information.database.currentRevision = revision.metadata.revision;
|
|
|
|
expect(getFileRepairRevisionActions(inspection, revision)).toMatchObject({
|
|
discardRevision: true,
|
|
discardBranch: false,
|
|
});
|
|
});
|
|
|
|
it("does not offer a storage action for a matching absent logical deletion", () => {
|
|
const { inspection, revision } = createInspection(
|
|
{
|
|
metadata: {
|
|
...createInspection().revision.metadata,
|
|
deleted: true,
|
|
},
|
|
contentReadable: true,
|
|
contentMatchesStorage: null,
|
|
loadedEntry: false,
|
|
},
|
|
{ exists: false }
|
|
);
|
|
|
|
expect(getFileRepairRevisionActions(inspection, revision)).toMatchObject({
|
|
applyLogicalDeletionToVault: false,
|
|
storeVaultOnBranch: false,
|
|
});
|
|
});
|
|
|
|
it("reports recorded, decoded, Vault-size, and timestamp differences", () => {
|
|
const { inspection, revision } = createInspection();
|
|
|
|
expect(getFileRepairRevisionComparison(inspection, revision)).toEqual({
|
|
recordedSize: 9,
|
|
decodedSize: 7,
|
|
recordedToDecodedSizeDifference: -2,
|
|
vaultSize: 12,
|
|
databaseToVaultSizeDifference: 5,
|
|
databaseMtime: 2_000,
|
|
vaultMtime: 5_500,
|
|
timestampDifferenceMs: 3_500,
|
|
timestampRelation: "vault-newer",
|
|
});
|
|
});
|
|
|
|
it("uses the same two-second timestamp comparison window as synchronisation", () => {
|
|
const { inspection, revision } = createInspection(
|
|
{
|
|
metadata: {
|
|
...createInspection().revision.metadata,
|
|
mtime: 3_001,
|
|
},
|
|
},
|
|
{
|
|
exists: true,
|
|
size: 12,
|
|
mtime: 3_999,
|
|
}
|
|
);
|
|
|
|
expect(getFileRepairRevisionComparison(inspection, revision)).toMatchObject({
|
|
timestampDifferenceMs: 998,
|
|
timestampRelation: "same-window",
|
|
});
|
|
});
|
|
});
|