diff --git a/packages/opencode/test/file/ignore.test.ts b/packages/opencode/test/file/ignore.test.ts index 6387ff63e4..ceab2a121c 100644 --- a/packages/opencode/test/file/ignore.test.ts +++ b/packages/opencode/test/file/ignore.test.ts @@ -1,4 +1,4 @@ -import { test, expect } from "bun:test" +import { describe, test, expect } from "bun:test" import { FileIgnore } from "../../src/file/ignore" test("match nested and non-nested", () => { @@ -8,3 +8,47 @@ test("match nested and non-nested", () => { expect(FileIgnore.match("node_modules/bar")).toBe(true) expect(FileIgnore.match("node_modules/bar/")).toBe(true) }) + +describe("FileIgnore.match: directory and file patterns", () => { + test("matches registered directory patterns beyond node_modules", () => { + expect(FileIgnore.match("dist/bundle.js")).toBe(true) + expect(FileIgnore.match("build/output.css")).toBe(true) + expect(FileIgnore.match(".git/config")).toBe(true) + expect(FileIgnore.match("__pycache__/module.pyc")).toBe(true) + expect(FileIgnore.match(".next/server/pages")).toBe(true) + expect(FileIgnore.match("out/index.html")).toBe(true) + expect(FileIgnore.match("bin/cli")).toBe(true) + // "desktop" is in FOLDERS — broad match, verify it works as specified + expect(FileIgnore.match("desktop/app.js")).toBe(true) + }) + + test("matches file glob patterns", () => { + expect(FileIgnore.match("src/editor.swp")).toBe(true) + expect(FileIgnore.match("deep/nested/file.swp")).toBe(true) + expect(FileIgnore.match("src/.DS_Store")).toBe(true) + expect(FileIgnore.match("cache.pyc")).toBe(true) + expect(FileIgnore.match("logs/app.log")).toBe(true) + expect(FileIgnore.match("tmp/upload.bin")).toBe(true) + }) + + test("does not match normal source files", () => { + expect(FileIgnore.match("src/index.ts")).toBe(false) + expect(FileIgnore.match("README.md")).toBe(false) + expect(FileIgnore.match("package.json")).toBe(false) + expect(FileIgnore.match("lib/utils.js")).toBe(false) + }) + + test("whitelist overrides directory match", () => { + expect(FileIgnore.match("node_modules/my-package/index.js", { whitelist: ["node_modules/**"] })).toBe(false) + }) + + test("extra patterns extend matching", () => { + expect(FileIgnore.match("config/.env")).toBe(false) + expect(FileIgnore.match("config/.env", { extra: ["**/.env"] })).toBe(true) + }) + + test("handles Windows-style path separators", () => { + expect(FileIgnore.match("node_modules\\package\\index.js")).toBe(true) + expect(FileIgnore.match("src\\.git\\config")).toBe(true) + }) +}) diff --git a/packages/opencode/test/id/id.test.ts b/packages/opencode/test/id/id.test.ts new file mode 100644 index 0000000000..f1294c41bb --- /dev/null +++ b/packages/opencode/test/id/id.test.ts @@ -0,0 +1,67 @@ +import { describe, test, expect } from "bun:test" +import { Identifier } from "../../src/id/id" + +describe("Identifier: monotonic ID generation", () => { + test("ascending IDs are strictly increasing", () => { + const ids: string[] = [] + for (let i = 0; i < 20; i++) { + ids.push(Identifier.ascending("session")) + } + for (let i = 1; i < ids.length; i++) { + expect(ids[i]! > ids[i - 1]!).toBe(true) + } + }) + + test("descending IDs are strictly decreasing", () => { + const ids: string[] = [] + for (let i = 0; i < 20; i++) { + ids.push(Identifier.descending("session")) + } + for (let i = 1; i < ids.length; i++) { + expect(ids[i]! < ids[i - 1]!).toBe(true) + } + }) + + test("ascending IDs have correct prefix for each type", () => { + expect(Identifier.ascending("session")).toStartWith("ses_") + expect(Identifier.ascending("message")).toStartWith("msg_") + expect(Identifier.ascending("part")).toStartWith("prt_") + expect(Identifier.ascending("permission")).toStartWith("per_") + expect(Identifier.ascending("tool")).toStartWith("tool_") + }) + + test("same-millisecond IDs are unique via counter", () => { + const ids = new Set() + for (let i = 0; i < 100; i++) { + ids.add(Identifier.ascending("session")) + } + expect(ids.size).toBe(100) + }) + + test("given parameter returns same ID when prefix matches", () => { + const id = Identifier.ascending("session") + expect(Identifier.ascending("session", id)).toBe(id) + }) + + test("given parameter throws when prefix does not match", () => { + const msgId = Identifier.ascending("message") + expect(() => Identifier.ascending("session", msgId)).toThrow() + }) + + test("timestamp values are relatively ordered for IDs created at different times", () => { + // timestamp() returns a truncated value (lower 48 bits of ts*4096), + // not the actual wall-clock time. But relative ordering is preserved, + // which is how it's used in practice (e.g., truncation.ts comparisons). + const id1 = Identifier.create("session", false, 1000000) + const id2 = Identifier.create("session", false, 2000000) + expect(Identifier.timestamp(id2)).toBeGreaterThan(Identifier.timestamp(id1)) + }) + + test("timestamp round-trips for small timestamps within 48-bit range", () => { + // For small timestamps (< 2^36 ≈ 68.7B), the value fits in 48 bits + // and round-trips exactly. Modern Date.now() values overflow this. + const smallTs = 1000000 + const id = Identifier.create("session", false, smallTs) + expect(Identifier.timestamp(id)).toBe(smallTs) + }) +})