mirror of
https://github.com/Nystik-gh/ignis.git
synced 2026-06-17 04:35:53 +00:00
add basic tests
This commit is contained in:
1230
package-lock.json
generated
1230
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,9 @@
|
||||
"scripts": {
|
||||
"build": "node build.js",
|
||||
"dev:server": "node server/index.js",
|
||||
"dev": "npm run build && npm run dev:server"
|
||||
"dev": "npm run build && npm run dev:server",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"archiver": "^7.0.1",
|
||||
@@ -22,6 +24,7 @@
|
||||
"esbuild-svelte": "^0.9.4",
|
||||
"lucide-svelte": "^0.577.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"svelte": "^4.2.20"
|
||||
"svelte": "^4.2.20",
|
||||
"vitest": "^4.1.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,3 +552,5 @@ router.get("/download-zip", async (req, res) => {
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
module.exports.resolveVaultPath = resolveVaultPath;
|
||||
module.exports.encodeContentDispositionFilename = encodeContentDispositionFilename;
|
||||
|
||||
109
server/routes/fs.test.mjs
Normal file
109
server/routes/fs.test.mjs
Normal file
@@ -0,0 +1,109 @@
|
||||
import path from "path";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { createRequire } from "module";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const {
|
||||
resolveVaultPath,
|
||||
encodeContentDispositionFilename,
|
||||
} = require("./fs.js");
|
||||
|
||||
// -- encodeContentDispositionFilename --------------------------------
|
||||
|
||||
describe("encodeContentDispositionFilename", () => {
|
||||
it("handles a plain ASCII filename", () => {
|
||||
expect(encodeContentDispositionFilename("report.pdf")).toBe(
|
||||
'attachment; filename="report.pdf"',
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves spaces in quotes", () => {
|
||||
expect(encodeContentDispositionFilename("my report.pdf")).toBe(
|
||||
'attachment; filename="my report.pdf"',
|
||||
);
|
||||
});
|
||||
|
||||
it("escapes double quotes", () => {
|
||||
const result = encodeContentDispositionFilename('file"name.txt');
|
||||
expect(result).toBe('attachment; filename="file\\"name.txt"');
|
||||
});
|
||||
|
||||
it("escapes backslashes", () => {
|
||||
const result = encodeContentDispositionFilename("path\\to\\file.txt");
|
||||
expect(result).toBe('attachment; filename="path\\\\to\\\\file.txt"');
|
||||
});
|
||||
|
||||
it("produces ASCII fallback and filename* for unicode", () => {
|
||||
const result = encodeContentDispositionFilename(
|
||||
"\u65E5\u672C\u8A9Enotes.md",
|
||||
);
|
||||
expect(result).toContain('filename="___notes.md"');
|
||||
expect(result).toContain("filename*=UTF-8''");
|
||||
expect(result).toContain("%E6%97%A5");
|
||||
});
|
||||
|
||||
it("replaces only non-ASCII in the fallback for mixed filenames", () => {
|
||||
const result = encodeContentDispositionFilename("report_2024\u5E74.pdf");
|
||||
expect(result).toContain('filename="report_2024_.pdf"');
|
||||
expect(result).toContain("filename*=UTF-8''");
|
||||
});
|
||||
|
||||
it("strips control characters", () => {
|
||||
const result = encodeContentDispositionFilename("bad\x00file\x1F.txt");
|
||||
expect(result).toBe('attachment; filename="badfile.txt"');
|
||||
});
|
||||
|
||||
it("does not crash on empty string", () => {
|
||||
const result = encodeContentDispositionFilename("");
|
||||
expect(result).toBe('attachment; filename=""');
|
||||
});
|
||||
});
|
||||
|
||||
// -- resolveVaultPath ------------------------------------------------
|
||||
|
||||
describe("resolveVaultPath", () => {
|
||||
const root = "/vaults/test";
|
||||
|
||||
it("resolves a simple relative path", () => {
|
||||
const result = resolveVaultPath(root, "notes/daily.md");
|
||||
expect(result).toBe(path.resolve(root, "notes/daily.md"));
|
||||
});
|
||||
|
||||
it("resolves empty string to vault root", () => {
|
||||
expect(resolveVaultPath(root, "")).toBe(path.resolve(root));
|
||||
});
|
||||
|
||||
it("allows a path that equals the vault root exactly", () => {
|
||||
expect(resolveVaultPath(root, "")).toBe(path.resolve(root));
|
||||
});
|
||||
|
||||
it("treats null input as vault root", () => {
|
||||
expect(resolveVaultPath(root, null)).toBe(path.resolve(root));
|
||||
});
|
||||
|
||||
it("treats undefined input as vault root", () => {
|
||||
expect(resolveVaultPath(root, undefined)).toBe(path.resolve(root));
|
||||
});
|
||||
|
||||
it("strips leading slashes", () => {
|
||||
const result = resolveVaultPath(root, "///notes/daily.md");
|
||||
expect(result).toBe(path.resolve(root, "notes/daily.md"));
|
||||
});
|
||||
|
||||
it("resolves ./ segments correctly", () => {
|
||||
const result = resolveVaultPath(root, "./notes/../notes/daily.md");
|
||||
expect(result).toBe(path.resolve(root, "notes/daily.md"));
|
||||
});
|
||||
|
||||
it("rejects ../ that escapes vault root", () => {
|
||||
expect(resolveVaultPath(root, "../")).toBe(null);
|
||||
});
|
||||
|
||||
it("rejects deep traversal", () => {
|
||||
expect(resolveVaultPath(root, "a/b/c/../../../../etc/passwd")).toBe(null);
|
||||
});
|
||||
|
||||
it("rejects traversal to a sibling vault with a shared prefix", () => {
|
||||
expect(resolveVaultPath(root, "../testing/foo")).toBe(null);
|
||||
});
|
||||
});
|
||||
60
src/shims/fs/metadata-cache.test.js
Normal file
60
src/shims/fs/metadata-cache.test.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { MetadataCache } from "./metadata-cache.js";
|
||||
|
||||
// -- Path normalization ----------------------------------------------
|
||||
|
||||
describe("MetadataCache path normalization", () => {
|
||||
it("converts backslashes to forward slashes", () => {
|
||||
const cache = new MetadataCache();
|
||||
cache.set("foo\\bar\\baz.md", { type: "file", size: 10 });
|
||||
expect(cache.has("foo/bar/baz.md")).toBe(true);
|
||||
});
|
||||
|
||||
it("strips leading and trailing slashes", () => {
|
||||
const cache = new MetadataCache();
|
||||
cache.set("/foo/bar/", { type: "file", size: 10 });
|
||||
expect(cache.has("foo/bar")).toBe(true);
|
||||
});
|
||||
|
||||
it("handles null and undefined as empty string", () => {
|
||||
const cache = new MetadataCache();
|
||||
cache.set(null, { type: "directory", size: 0 });
|
||||
expect(cache.has("")).toBe(true);
|
||||
expect(cache.has(undefined)).toBe(true);
|
||||
});
|
||||
|
||||
it("normalizes //foo\\\\bar// to foo/bar", () => {
|
||||
const cache = new MetadataCache();
|
||||
cache.set("//foo\\bar//", { type: "file", size: 5 });
|
||||
expect(cache.has("foo/bar")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// -- Operations ------------------------------------------------------
|
||||
|
||||
describe("MetadataCache populate and merge", () => {
|
||||
it.todo("populate() clears existing entries");
|
||||
it.todo("merge() preserves existing entries");
|
||||
it.todo("populate then merge -- pre-existing entries survive merge");
|
||||
});
|
||||
|
||||
describe("MetadataCache toStat", () => {
|
||||
it.todo("returns correct shape with all expected fields and methods");
|
||||
it.todo("returns null for missing paths");
|
||||
it.todo("constructs dates from zero when mtime/ctime are missing");
|
||||
});
|
||||
|
||||
describe("MetadataCache readdir", () => {
|
||||
it.todo("root readdir returns top-level entries");
|
||||
it.todo("nested dir returns only direct children, not grandchildren");
|
||||
it.todo(
|
||||
"readdir of foo does not include foobar entries (prefix false-match)",
|
||||
);
|
||||
it.todo("infers directory type for paths with no direct map entry");
|
||||
it.todo("returns empty array for path with no children");
|
||||
it.todo("returns empty array for nonexistent path");
|
||||
});
|
||||
|
||||
describe("MetadataCache rename", () => {
|
||||
it.todo("rename file: old path gone, new path present with same metadata");
|
||||
});
|
||||
Reference in New Issue
Block a user