mirror of
https://github.com/beatsad/Vault2Dify.git
synced 2026-07-22 07:46:49 +00:00
221 lines
7.8 KiB
JavaScript
221 lines
7.8 KiB
JavaScript
import assert from "node:assert/strict";
|
||
import { build } from "esbuild";
|
||
import { pathToFileURL } from "node:url";
|
||
|
||
const outfile = "tests/.tmp-sync-utils.mjs";
|
||
|
||
await build({
|
||
entryPoints: ["src/sync-utils.ts"],
|
||
bundle: true,
|
||
platform: "node",
|
||
format: "esm",
|
||
outfile,
|
||
logLevel: "silent",
|
||
});
|
||
|
||
const utils = await import(pathToFileURL(`${process.cwd()}/${outfile}`).href);
|
||
|
||
function test(name, fn) {
|
||
try {
|
||
fn();
|
||
console.log(`ok - ${name}`);
|
||
} catch (error) {
|
||
console.error(`not ok - ${name}`);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
test("sanitizeBaseUrl trims spaces, trailing slashes, and known Dify API paths", () => {
|
||
assert.equal(utils.sanitizeBaseUrl(" https://dify.example.com/v1/ "), "https://dify.example.com");
|
||
assert.equal(utils.sanitizeBaseUrl("https://dify.example.com/v1/datasets"), "https://dify.example.com");
|
||
assert.equal(utils.sanitizeBaseUrl("http://dify-host.example.test:5000///"), "http://dify-host.example.test:5000");
|
||
});
|
||
|
||
test("sanitizeBaseUrl adds http when the scheme is omitted", () => {
|
||
assert.equal(utils.sanitizeBaseUrl("dify-host.example.test:5000"), "http://dify-host.example.test:5000");
|
||
assert.equal(utils.sanitizeBaseUrl(""), "");
|
||
});
|
||
|
||
test("normalizeFolderPath removes surrounding spaces and slashes", () => {
|
||
assert.equal(utils.normalizeFolderPath(" /Work/ProjectA/ "), "Work/ProjectA");
|
||
assert.equal(utils.normalizeFolderPath(""), "");
|
||
});
|
||
|
||
test("pathMatchesFolder treats the empty folder as the whole vault", () => {
|
||
assert.equal(utils.pathMatchesFolder("Work/ProjectA/Note.md", ""), true);
|
||
});
|
||
|
||
test("pathMatchesFolder matches the exact folder and descendants only", () => {
|
||
assert.equal(utils.pathMatchesFolder("Work/ProjectA", "Work/ProjectA"), true);
|
||
assert.equal(utils.pathMatchesFolder("Work/ProjectA/Note.md", "Work/ProjectA"), true);
|
||
assert.equal(utils.pathMatchesFolder("Work/ProjectArchive/Note.md", "Work/ProjectA"), false);
|
||
});
|
||
|
||
test("pathMatchesFolder supports single Markdown note mappings", () => {
|
||
assert.equal(utils.pathMatchesFolder("Work/Note.md", "Work/Note.md"), true);
|
||
assert.equal(utils.pathMatchesFolder("Work/Other.md", "Work/Note.md"), false);
|
||
assert.equal(utils.pathMatchesFolder("Work/Note.md/Child.md", "Work/Note.md"), false);
|
||
});
|
||
|
||
test("splitIds supports English and Chinese separators and removes duplicates", () => {
|
||
assert.deepEqual(utils.splitIds(" kb1,kb2,kb1\nkb3;kb4;kb2 "), ["kb1", "kb2", "kb3", "kb4"]);
|
||
});
|
||
|
||
test("clampNumber keeps values within range and falls back for invalid input", () => {
|
||
assert.equal(utils.clampNumber(10, 1, 20, 5), 10);
|
||
assert.equal(utils.clampNumber(0, 1, 20, 5), 1);
|
||
assert.equal(utils.clampNumber(25, 1, 20, 5), 20);
|
||
assert.equal(utils.clampNumber(Number.NaN, 1, 20, 5), 5);
|
||
});
|
||
|
||
test("getRecordKey combines dataset id and file path without losing slashes", () => {
|
||
assert.equal(utils.getRecordKey("dataset-1", "Work/ProjectA/Note.md"), "dataset-1::Work/ProjectA/Note.md");
|
||
});
|
||
|
||
test("migrateRenamedSyncRecords preserves old remote name so Dify can be renamed", () => {
|
||
const records = new Map([
|
||
[utils.getRecordKey("dataset-1", "Work/Old.md"), {
|
||
filePath: "Work/Old.md",
|
||
datasetId: "dataset-1",
|
||
documentId: "doc-1",
|
||
remoteName: "Work/Old.md",
|
||
hash: "hash-1",
|
||
lastModified: 100,
|
||
lastSyncedAt: "2026-06-07T03:00:00.000Z",
|
||
}],
|
||
]);
|
||
|
||
const changed = utils.migrateRenamedSyncRecords(records, "Work/Old.md", "Work/New.md", (path) => path);
|
||
|
||
assert.equal(changed, true);
|
||
assert.equal(records.has(utils.getRecordKey("dataset-1", "Work/Old.md")), false);
|
||
assert.deepEqual(records.get(utils.getRecordKey("dataset-1", "Work/New.md")), {
|
||
filePath: "Work/New.md",
|
||
datasetId: "dataset-1",
|
||
documentId: "doc-1",
|
||
remoteName: "Work/Old.md",
|
||
hash: "hash-1",
|
||
lastModified: 100,
|
||
lastSyncedAt: "2026-06-07T03:00:00.000Z",
|
||
deletedLocal: false,
|
||
});
|
||
});
|
||
|
||
test("recoverDeletedSyncRecordForCreatedFile reuses a recently deleted same-content document", () => {
|
||
const records = new Map([
|
||
[utils.getRecordKey("dataset-1", "Work/Old.md"), {
|
||
filePath: "Work/Old.md",
|
||
datasetId: "dataset-1",
|
||
documentId: "doc-1",
|
||
remoteName: "Work/Old.md",
|
||
hash: "same-hash",
|
||
lastModified: 100,
|
||
lastSyncedAt: "2026-06-07T03:00:10.000Z",
|
||
deletedLocal: true,
|
||
}],
|
||
]);
|
||
|
||
const recovered = utils.recoverDeletedSyncRecordForCreatedFile(
|
||
records,
|
||
"dataset-1",
|
||
"Work/New.md",
|
||
"same-hash",
|
||
(path) => path,
|
||
new Date("2026-06-07T03:00:20.000Z"),
|
||
30000,
|
||
);
|
||
|
||
assert.equal(recovered, true);
|
||
assert.equal(records.has(utils.getRecordKey("dataset-1", "Work/Old.md")), false);
|
||
assert.deepEqual(records.get(utils.getRecordKey("dataset-1", "Work/New.md")), {
|
||
filePath: "Work/New.md",
|
||
datasetId: "dataset-1",
|
||
documentId: "doc-1",
|
||
remoteName: "Work/Old.md",
|
||
hash: "same-hash",
|
||
lastModified: 100,
|
||
lastSyncedAt: "2026-06-07T03:00:10.000Z",
|
||
deletedLocal: false,
|
||
});
|
||
});
|
||
|
||
test("getDatasetIdsForPathFromMappings returns enabled matching datasets only", () => {
|
||
const mappings = [
|
||
{ folder: "Work", datasetIds: ["kb-work"], enabled: true },
|
||
{ folder: "Work/Archive", datasetIds: ["kb-archive"], enabled: false },
|
||
{ folder: "Reading", datasetIds: ["kb-reading"], enabled: true },
|
||
];
|
||
|
||
assert.deepEqual(utils.getDatasetIdsForPathFromMappings("Work/Note.md", mappings), ["kb-work"]);
|
||
assert.deepEqual(utils.getDatasetIdsForPathFromMappings("Reading/Book.md", mappings), ["kb-reading"]);
|
||
assert.deepEqual(utils.getDatasetIdsForPathFromMappings("Inbox/Note.md", mappings), []);
|
||
});
|
||
|
||
test("getDatasetIdsForPathFromMappings supports exact note mappings", () => {
|
||
const mappings = [
|
||
{ folder: "Work/Note.md", datasetIds: ["kb-note"], enabled: true },
|
||
{ folder: "Work", datasetIds: ["kb-work"], enabled: true },
|
||
];
|
||
|
||
assert.deepEqual(utils.getDatasetIdsForPathFromMappings("Work/Note.md", mappings), ["kb-note", "kb-work"]);
|
||
assert.deepEqual(utils.getDatasetIdsForPathFromMappings("Work/Other.md", mappings), ["kb-work"]);
|
||
});
|
||
|
||
test("getDatasetIdsForPathFromMappings deduplicates datasets across matching mappings", () => {
|
||
const mappings = [
|
||
{ folder: "", datasetIds: ["kb-all", "kb-shared"], enabled: true },
|
||
{ folder: "Work", datasetIds: ["kb-shared", "kb-work"], enabled: true },
|
||
];
|
||
|
||
assert.deepEqual(utils.getDatasetIdsForPathFromMappings("Work/Note.md", mappings), ["kb-all", "kb-shared", "kb-work"]);
|
||
});
|
||
|
||
test("removeMappingById only removes the local mapping entry", () => {
|
||
const mappings = [
|
||
{ id: "map-1", folder: "Work", datasetIds: ["kb-work"], enabled: true },
|
||
{ id: "map-2", folder: "Reading", datasetIds: ["kb-reading"], enabled: true },
|
||
];
|
||
const removed = utils.removeMappingById(mappings, "map-1");
|
||
|
||
assert.deepEqual(removed, [
|
||
{ id: "map-2", folder: "Reading", datasetIds: ["kb-reading"], enabled: true },
|
||
]);
|
||
assert.equal(mappings.length, 2);
|
||
assert.equal(typeof utils.deleteDocument, "undefined");
|
||
});
|
||
|
||
test("buildSettingsReviewSummary returns zero metrics and muted state before any sync", () => {
|
||
const summary = utils.buildSettingsReviewSummary({
|
||
connectionHealth: { status: "unknown" },
|
||
});
|
||
|
||
assert.deepEqual(summary.metrics, [
|
||
{ key: "total", value: 0, tone: "accent" },
|
||
{ key: "synced", value: 0, tone: "success" },
|
||
{ key: "skipped", value: 0, tone: "warning" },
|
||
{ key: "failed", value: 0, tone: "error" },
|
||
]);
|
||
assert.equal(summary.connectionTone, "muted");
|
||
assert.equal(summary.hasRecentSync, false);
|
||
});
|
||
|
||
test("buildSettingsReviewSummary maps recent sync and connection health into review tones", () => {
|
||
const summary = utils.buildSettingsReviewSummary({
|
||
connectionHealth: { status: "connected" },
|
||
recentSyncResult: {
|
||
total: 7,
|
||
synced: 4,
|
||
skipped: 2,
|
||
failed: 1,
|
||
},
|
||
});
|
||
|
||
assert.deepEqual(summary.metrics, [
|
||
{ key: "total", value: 7, tone: "accent" },
|
||
{ key: "synced", value: 4, tone: "success" },
|
||
{ key: "skipped", value: 2, tone: "warning" },
|
||
{ key: "failed", value: 1, tone: "error" },
|
||
]);
|
||
assert.equal(summary.connectionTone, "success");
|
||
assert.equal(summary.hasRecentSync, true);
|
||
});
|