/** * InMemory VersionStore 语义单测(契约 C1:S1/S2/S7 + 计划 D16/S4)。 * mock 必须如实表达:冲突走返回值、baseVersion=null 表新建、init 幂等、 * 文件级版本互不影响、同仓库写操作串行。 */ import { describe, expect, it } from "vitest"; import { createInMemoryVersionStore } from "../../src/database/filelib/versionStore.js"; import { FileLibError } from "../../src/database/filelib/model.js"; const DIR = "/repos/p1"; describe("InMemoryVersionStore · C1 语义", () => { it("init 幂等(S7);未 init 的仓库操作报 repo_not_found", async () => { const store = createInMemoryVersionStore(); await expect(store.head(DIR, "a.md")).rejects.toThrowError(FileLibError); await store.init(DIR); await store.init(DIR); // 幂等,不报错、不重建 await store.commit(DIR, "a.md", { baseVersion: null, content: "hello" }); await store.init(DIR); // 已有内容后再 init 也不清空 expect(await store.head(DIR, "a.md")).toBe("v1"); }); it("新建(baseVersion null)→ ok;head/read 命中", async () => { const store = createInMemoryVersionStore(); await store.init(DIR); const r = await store.commit(DIR, "a.md", { baseVersion: null, content: "v1 内容", author: "u1" }); expect(r).toEqual({ status: "ok", version: "v1" }); expect((await store.read(DIR, "a.md")).toString()).toBe("v1 内容"); }); it("对已存在文件再次『新建』 → conflict(S2)", async () => { const store = createInMemoryVersionStore(); await store.init(DIR); await store.commit(DIR, "a.md", { baseVersion: null, content: "1" }); const r = await store.commit(DIR, "a.md", { baseVersion: null, content: "2" }); expect(r).toEqual({ status: "conflict", currentVersion: "v1" }); }); it("baseVersion 落后于当前 → conflict 并带 currentVersion(S1)", async () => { const store = createInMemoryVersionStore(); await store.init(DIR); await store.commit(DIR, "a.md", { baseVersion: null, content: "1" }); const ok = await store.commit(DIR, "a.md", { baseVersion: "v1", content: "2" }); expect(ok.status).toBe("ok"); const stale = await store.commit(DIR, "a.md", { baseVersion: "v1", content: "3" }); expect(stale).toEqual({ status: "conflict", currentVersion: "v2" }); }); it("正确 base 连续提交;旧版本仍可读", async () => { const store = createInMemoryVersionStore(); await store.init(DIR); await store.commit(DIR, "a.md", { baseVersion: null, content: "一" }); await store.commit(DIR, "a.md", { baseVersion: "v1", content: "二" }); expect((await store.read(DIR, "a.md", "v1")).toString()).toBe("一"); expect((await store.read(DIR, "a.md")).toString()).toBe("二"); }); it("D16 文件级版本:文件 A 的提交不影响文件 B 的 base", async () => { const store = createInMemoryVersionStore(); await store.init(DIR); await store.commit(DIR, "a.md", { baseVersion: null, content: "A1" }); const b1 = await store.commit(DIR, "b.md", { baseVersion: null, content: "B1" }); await store.commit(DIR, "a.md", { baseVersion: "v1", content: "A2" }); // A 前进到 v3 // B 仍以自己的 head 为 base,不受 A 推进影响 const b2 = await store.commit(DIR, "b.md", { baseVersion: b1.status === "ok" ? b1.version : "", content: "B2", }); expect(b2.status).toBe("ok"); }); it("remove:正确 base → ok;head 随后 404;stale base → conflict", async () => { const store = createInMemoryVersionStore(); await store.init(DIR); await store.commit(DIR, "a.md", { baseVersion: null, content: "1" }); await store.commit(DIR, "a.md", { baseVersion: "v1", content: "2" }); expect(await store.remove(DIR, "a.md", "v1")).toEqual({ status: "conflict", currentVersion: "v2" }); expect((await store.remove(DIR, "a.md", "v2")).status).toBe("ok"); await expect(store.head(DIR, "a.md")).rejects.toThrowError(FileLibError); await expect(store.remove(DIR, "a.md", "v3")).rejects.toThrowError(FileLibError); }); it("history 新→旧,支持 limit;list 排除已删并按前缀过滤", async () => { const store = createInMemoryVersionStore(); await store.init(DIR); await store.commit(DIR, "docs/a.md", { baseVersion: null, content: "1", message: "初版" }); await store.commit(DIR, "docs/a.md", { baseVersion: "v1", content: "2", message: "二版" }); await store.commit(DIR, "other/b.md", { baseVersion: null, content: "x" }); const h = await store.history(DIR, "docs/a.md"); expect(h.map((v) => v.message)).toEqual(["二版", "初版"]); expect((await store.history(DIR, "docs/a.md", 1)).map((v) => v.message)).toEqual(["二版"]); expect((await store.list(DIR)).map((f) => f.path)).toEqual(["docs/a.md", "other/b.md"]); expect((await store.list(DIR, "docs/")).map((f) => f.path)).toEqual(["docs/a.md"]); await store.remove(DIR, "other/b.md", "v3"); expect((await store.list(DIR)).map((f) => f.path)).toEqual(["docs/a.md"]); }); it("S4 同仓库写串行:并发同 base 提交,恰好一成一冲突", async () => { const store = createInMemoryVersionStore(); await store.init(DIR); await store.commit(DIR, "a.md", { baseVersion: null, content: "1" }); const [r1, r2] = await Promise.all([ store.commit(DIR, "a.md", { baseVersion: "v1", content: "x" }), store.commit(DIR, "a.md", { baseVersion: "v1", content: "y" }), ]); const statuses = [r1.status, r2.status].sort(); expect(statuses).toEqual(["conflict", "ok"]); }); it("diff 输出含增删行", async () => { const store = createInMemoryVersionStore(); await store.init(DIR); await store.commit(DIR, "a.md", { baseVersion: null, content: "keep\nold" }); await store.commit(DIR, "a.md", { baseVersion: "v1", content: "keep\nnew" }); const d = await store.diff(DIR, "a.md", "v1", "v2"); expect(d).toContain("-old"); expect(d).toContain("+new"); expect(d).toContain(" keep"); }); });