| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- /**
- * Store helper-level unit tests (pure logic, no model/runtime dependency).
- */
- import { describe, test, expect } from "vitest";
- import {
- homedir,
- resolve,
- getDefaultDbPath,
- getPwd,
- getRealPath,
- isVirtualPath,
- parseVirtualPath,
- normalizeVirtualPath,
- normalizeDocid,
- isDocid,
- handelize,
- cleanupOrphanedVectors,
- } from "../src/store";
- // =============================================================================
- // Path Utilities
- // =============================================================================
- describe("Path Utilities", () => {
- test("homedir returns HOME environment variable", () => {
- expect(homedir()).toBe(process.env.HOME || "/tmp");
- });
- test("resolve handles absolute paths", () => {
- expect(resolve("/foo/bar")).toBe("/foo/bar");
- expect(resolve("/foo", "/bar")).toBe("/bar");
- });
- test("resolve handles relative paths", () => {
- const pwd = process.env.PWD || process.cwd();
- expect(resolve("foo")).toBe(`${pwd}/foo`);
- expect(resolve("foo", "bar")).toBe(`${pwd}/foo/bar`);
- });
- test("resolve normalizes . and ..", () => {
- expect(resolve("/foo/bar/./baz")).toBe("/foo/bar/baz");
- expect(resolve("/foo/bar/../baz")).toBe("/foo/baz");
- expect(resolve("/foo/bar/../../baz")).toBe("/baz");
- });
- test("getDefaultDbPath throws in test mode without INDEX_PATH", () => {
- const originalIndexPath = process.env.INDEX_PATH;
- delete process.env.INDEX_PATH;
- expect(() => getDefaultDbPath()).toThrow("Database path not set");
- if (originalIndexPath) {
- process.env.INDEX_PATH = originalIndexPath;
- }
- });
- test("getDefaultDbPath uses INDEX_PATH when set", () => {
- const originalIndexPath = process.env.INDEX_PATH;
- process.env.INDEX_PATH = "/tmp/test-index.sqlite";
- expect(getDefaultDbPath()).toBe("/tmp/test-index.sqlite");
- expect(getDefaultDbPath("custom")).toBe("/tmp/test-index.sqlite");
- if (originalIndexPath) {
- process.env.INDEX_PATH = originalIndexPath;
- } else {
- delete process.env.INDEX_PATH;
- }
- });
- test("getPwd returns current working directory", () => {
- const pwd = getPwd();
- expect(pwd).toBeTruthy();
- expect(typeof pwd).toBe("string");
- });
- test("getRealPath resolves symlinks", () => {
- const result = getRealPath("/tmp");
- expect(result).toBeTruthy();
- expect(result === "/tmp" || result === "/private/tmp").toBe(true);
- });
- });
- // =============================================================================
- // Handelize Tests
- // =============================================================================
- describe("cleanupOrphanedVectors", () => {
- test("returns 0 when vec table exists in schema but sqlite-vec is unavailable", () => {
- const prepare = (sql: string) => {
- if (sql.includes("sqlite_master") && sql.includes("vectors_vec")) {
- return { get: () => ({ name: "vectors_vec" }) };
- }
- if (sql.includes("SELECT 1 FROM vectors_vec LIMIT 0")) {
- return { get: () => { throw new Error("no such module: vec0"); } };
- }
- throw new Error(`Unexpected SQL in test: ${sql}`);
- };
- const db = {
- prepare,
- exec: () => {
- throw new Error("cleanup should not execute vector deletes when sqlite-vec is unavailable");
- },
- } as any;
- expect(cleanupOrphanedVectors(db)).toBe(0);
- });
- });
- // =============================================================================
- // Handelize Tests
- // =============================================================================
- describe("handelize", () => {
- test("preserves original case", () => {
- expect(handelize("README.md")).toBe("README.md");
- expect(handelize("MyFile.MD")).toBe("MyFile.MD");
- });
- test("preserves folder structure", () => {
- expect(handelize("a/b/c/d.md")).toBe("a/b/c/d.md");
- expect(handelize("docs/api/README.md")).toBe("docs/api/README.md");
- });
- test("replaces non-word characters with dash", () => {
- expect(handelize("hello world.md")).toBe("hello-world.md");
- expect(handelize("file (1).md")).toBe("file-1.md");
- expect(handelize("foo@bar#baz.md")).toBe("foo-bar-baz.md");
- });
- test("collapses multiple special chars into single dash", () => {
- expect(handelize("hello world.md")).toBe("hello-world.md");
- expect(handelize("foo---bar.md")).toBe("foo-bar.md");
- expect(handelize("a - b.md")).toBe("a-b.md");
- });
- test("removes leading and trailing dashes from segments", () => {
- expect(handelize("-hello-.md")).toBe("hello.md");
- expect(handelize("--test--.md")).toBe("test.md");
- expect(handelize("a/-b-/c.md")).toBe("a/b/c.md");
- });
- test("converts triple underscore to folder separator", () => {
- expect(handelize("foo___bar.md")).toBe("foo/bar.md");
- expect(handelize("notes___2025___january.md")).toBe("notes/2025/january.md");
- expect(handelize("a/b___c/d.md")).toBe("a/b/c/d.md");
- });
- test("handles complex real-world meeting notes", () => {
- const complexName = "Money Movement Licensing Review - 2025/11/19 10:25 EST - Notes by Gemini.md";
- const result = handelize(complexName);
- expect(result).toBe("Money-Movement-Licensing-Review-2025-11-19-10-25-EST-Notes-by-Gemini.md");
- expect(result).not.toContain(" ");
- expect(result).not.toContain("/");
- expect(result).not.toContain(":");
- });
- test("handles unicode characters", () => {
- expect(handelize("日本語.md")).toBe("日本語.md");
- expect(handelize("Зоны и проекты.md")).toBe("Зоны-и-проекты.md");
- expect(handelize("café-notes.md")).toBe("café-notes.md");
- expect(handelize("naïve.md")).toBe("naïve.md");
- expect(handelize("日本語-notes.md")).toBe("日本語-notes.md");
- });
- test("handles emoji filenames (issue #302)", () => {
- // Emoji-only filenames should convert to hex codepoints
- expect(handelize("🐘.md")).toBe("1f418.md");
- expect(handelize("🎉.md")).toBe("1f389.md");
- // Emoji mixed with text
- expect(handelize("notes 🐘.md")).toBe("notes-1f418.md");
- expect(handelize("🐘 elephant.md")).toBe("1f418-elephant.md");
- // Multiple emojis
- expect(handelize("🐘🎉.md")).toBe("1f418-1f389.md");
- // Emoji in directory names
- expect(handelize("🐘/notes.md")).toBe("1f418/notes.md");
- });
- test("handles dates and times in filenames", () => {
- expect(handelize("meeting-2025-01-15.md")).toBe("meeting-2025-01-15.md");
- expect(handelize("notes 2025/01/15.md")).toBe("notes-2025/01/15.md");
- expect(handelize("call_10:30_AM.md")).toBe("call-10-30-AM.md");
- });
- test("handles special project naming patterns", () => {
- expect(handelize("PROJECT_ABC_v2.0.md")).toBe("PROJECT-ABC-v2.0.md");
- expect(handelize("[WIP] Feature Request.md")).toBe("WIP-Feature-Request.md");
- expect(handelize("(DRAFT) Proposal v1.md")).toBe("DRAFT-Proposal-v1.md");
- });
- test("handles symbol-only route filenames", () => {
- expect(handelize("routes/api/auth/$.ts")).toBe("routes/api/auth/$.ts");
- expect(handelize("app/routes/$id.tsx")).toBe("app/routes/$id.tsx");
- });
- test("filters out empty segments", () => {
- expect(handelize("a//b/c.md")).toBe("a/b/c.md");
- expect(handelize("/a/b/")).toBe("a/b");
- expect(handelize("///test///")).toBe("test");
- });
- test("throws error for invalid inputs", () => {
- expect(() => handelize("" )).toThrow("path cannot be empty");
- expect(() => handelize(" ")).toThrow("path cannot be empty");
- expect(() => handelize(".md")).toThrow("no valid filename content");
- expect(() => handelize("...")).toThrow("no valid filename content");
- expect(() => handelize("___")).toThrow("no valid filename content");
- });
- test("handles minimal valid inputs", () => {
- expect(handelize("a")).toBe("a");
- expect(handelize("1")).toBe("1");
- expect(handelize("a.md")).toBe("a.md");
- });
- test("normalizes virtual paths", () => {
- expect(normalizeVirtualPath("qmd://docs/readme.md")).toBe("qmd://docs/readme.md");
- expect(normalizeVirtualPath("docs/readme.md")).toBe("docs/readme.md");
- });
- test("detects virtual paths", () => {
- expect(isVirtualPath("qmd://docs/readme.md")).toBe(true);
- expect(isVirtualPath("/tmp/file.md")).toBe(false);
- });
- test("parses virtual paths", () => {
- expect(parseVirtualPath("qmd://docs/readme.md")).toEqual({
- collectionName: "docs",
- path: "readme.md",
- });
- });
- test("normalizes docids", () => {
- expect(normalizeDocid("123456")).toBe("123456");
- expect(normalizeDocid("#123456")).toBe("123456");
- });
- test("checks docid validity", () => {
- expect(isDocid("123456")).toBe(true);
- expect(isDocid("#123456")).toBe(true);
- expect(isDocid("bad-id")).toBe(false);
- expect(isDocid("12345")).toBe(false);
- });
- });
|