mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-08-24 12:27:06 +00:00
141 lines
6.9 KiB
TypeScript
141 lines
6.9 KiB
TypeScript
import { fsPromises, os, path } from "@vrtmrz/livesync-commonlib/node";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import type { FilePath } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
|
import { NodeFileSystemAdapter } from "./NodeFileSystemAdapter";
|
|
import { NodeVaultAdapter } from "./NodeVaultAdapter";
|
|
|
|
describe("NodeVaultAdapter.rename", () => {
|
|
it("changes the directory entry case without changing the content", async () => {
|
|
const directory = await fsPromises.mkdtemp(path.join(os.tmpdir(), "livesync-case-rename-"));
|
|
try {
|
|
await fsPromises.writeFile(path.join(directory, "Calculus.md"), "content", "utf8");
|
|
const adapter = new NodeVaultAdapter(directory);
|
|
const file = {
|
|
path: "Calculus.md" as FilePath,
|
|
stat: { ctime: 1, mtime: 2, size: 7, type: "file" as const },
|
|
};
|
|
|
|
await adapter.rename(file, "calculus.md");
|
|
|
|
expect(await fsPromises.readdir(directory)).toEqual(["calculus.md"]);
|
|
expect(await fsPromises.readFile(path.join(directory, "calculus.md"), "utf8")).toBe("content");
|
|
expect(file.path).toBe("calculus.md");
|
|
} finally {
|
|
await fsPromises.rm(directory, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("does not move a file through a symbolic link outside the vault root", async () => {
|
|
const directory = await fsPromises.mkdtemp(path.join(os.tmpdir(), "livesync-rename-root-"));
|
|
const outsideDirectory = await fsPromises.mkdtemp(path.join(os.tmpdir(), "livesync-rename-outside-"));
|
|
try {
|
|
await fsPromises.writeFile(path.join(directory, "source.md"), "content", "utf8");
|
|
await fsPromises.symlink(
|
|
outsideDirectory,
|
|
path.join(directory, "linked"),
|
|
process.platform === "win32" ? "junction" : "dir"
|
|
);
|
|
const adapter = new NodeVaultAdapter(directory);
|
|
const file = {
|
|
path: "source.md" as FilePath,
|
|
stat: { ctime: 1, mtime: 2, size: 7, type: "file" as const },
|
|
};
|
|
|
|
await expect(adapter.rename(file, "linked/moved.md")).rejects.toThrow(/symbolic link/i);
|
|
|
|
await expect(fsPromises.readFile(path.join(directory, "source.md"), "utf8")).resolves.toBe("content");
|
|
await expect(fsPromises.stat(path.join(outsideDirectory, "moved.md"))).rejects.toMatchObject({
|
|
code: "ENOENT",
|
|
});
|
|
} finally {
|
|
await fsPromises.rm(directory, { recursive: true, force: true });
|
|
await fsPromises.rm(outsideDirectory, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("does not modify a file through a symbolic link outside the vault root", async () => {
|
|
const directory = await fsPromises.mkdtemp(path.join(os.tmpdir(), "livesync-modify-root-"));
|
|
const outsideDirectory = await fsPromises.mkdtemp(path.join(os.tmpdir(), "livesync-modify-outside-"));
|
|
try {
|
|
await fsPromises.writeFile(path.join(outsideDirectory, "victim.md"), "before", "utf8");
|
|
await fsPromises.symlink(
|
|
outsideDirectory,
|
|
path.join(directory, "linked"),
|
|
process.platform === "win32" ? "junction" : "dir"
|
|
);
|
|
const adapter = new NodeVaultAdapter(directory);
|
|
const file = {
|
|
path: "linked/victim.md" as FilePath,
|
|
stat: { ctime: 1, mtime: 2, size: 6, type: "file" as const },
|
|
};
|
|
|
|
await expect(adapter.modify(file, "after")).rejects.toThrow(/symbolic link/i);
|
|
|
|
await expect(fsPromises.readFile(path.join(outsideDirectory, "victim.md"), "utf8")).resolves.toBe("before");
|
|
} finally {
|
|
await fsPromises.rm(directory, { recursive: true, force: true });
|
|
await fsPromises.rm(outsideDirectory, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("NodeFileSystemAdapter path case", () => {
|
|
it("finds the stored case and refreshes the cache after a case-only rename", async () => {
|
|
const directory = await fsPromises.mkdtemp(path.join(os.tmpdir(), "livesync-case-cache-"));
|
|
try {
|
|
await fsPromises.writeFile(path.join(directory, "Calculus.md"), "content", "utf8");
|
|
const adapter = new NodeFileSystemAdapter(directory);
|
|
|
|
await expect(adapter.getAbstractFileByPath("calculus.md")).resolves.toBeNull();
|
|
const existingFile = await adapter.getAbstractFileByPathInsensitive("calculus.md");
|
|
expect(existingFile?.path).toBe("Calculus.md");
|
|
if (!existingFile) throw new Error("Expected to find Calculus.md case-insensitively");
|
|
const renamedFile = await adapter.renameFile(existingFile, "calculus.md");
|
|
|
|
expect(renamedFile.path).toBe("calculus.md");
|
|
expect((await adapter.getFiles()).map((file) => file.path)).toEqual(["calculus.md"]);
|
|
expect(await fsPromises.readdir(directory)).toEqual(["calculus.md"]);
|
|
} finally {
|
|
await fsPromises.rm(directory, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("reports directory scan failures through the injected diagnostic callback", async () => {
|
|
const directory = await fsPromises.mkdtemp(path.join(os.tmpdir(), "livesync-scan-diagnostic-"));
|
|
const missingDirectory = path.join(directory, "missing");
|
|
const reportDiagnostic = vi.fn();
|
|
try {
|
|
const adapter = new NodeFileSystemAdapter(missingDirectory, reportDiagnostic);
|
|
|
|
await adapter.scanDirectory();
|
|
|
|
expect(reportDiagnostic).toHaveBeenCalledWith(
|
|
`Error scanning directory ${missingDirectory}:`,
|
|
expect.any(Error)
|
|
);
|
|
} finally {
|
|
await fsPromises.rm(directory, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("does not discover a file through a symbolic link outside the vault root", async () => {
|
|
const directory = await fsPromises.mkdtemp(path.join(os.tmpdir(), "livesync-discovery-root-"));
|
|
const outsideDirectory = await fsPromises.mkdtemp(path.join(os.tmpdir(), "livesync-discovery-outside-"));
|
|
try {
|
|
await fsPromises.writeFile(path.join(outsideDirectory, "outside.md"), "content", "utf8");
|
|
await fsPromises.symlink(
|
|
outsideDirectory,
|
|
path.join(directory, "linked"),
|
|
process.platform === "win32" ? "junction" : "dir"
|
|
);
|
|
const adapter = new NodeFileSystemAdapter(directory);
|
|
|
|
await expect(adapter.getAbstractFileByPath("linked/outside.md")).resolves.toBeNull();
|
|
await expect(adapter.getFiles()).resolves.toEqual([]);
|
|
} finally {
|
|
await fsPromises.rm(directory, { recursive: true, force: true });
|
|
await fsPromises.rm(outsideDirectory, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|