import { describe, it, expect, beforeEach } from "vitest"; import { FsTaskRepository } from "@/storage/FsTaskRepository"; import { InMemoryFileSystem } from "../helpers/InMemoryFileSystem"; import { ITaskRepository } from "@/ports"; import { Task } from "@/model/task"; describe("FsTaskRepository", () => { let fs: InMemoryFileSystem; let repo: ITaskRepository; const folder = "/tasks"; const sampleTask = { title: "Fix login bug", description: "Token validation error", status: "todo" as const, priority: "high" as const, tags: ["bug", "frontend"], assignee: "ivanov", }; beforeEach(() => { fs = new InMemoryFileSystem(); repo = new FsTaskRepository(fs, ".task.md"); }); it("creates a task and writes it as a .task.md file", async () => { const task = await repo.create(folder, sampleTask); expect(task.id).toBeTruthy(); expect(task.title).toBe("Fix login bug"); expect(task.created).toBeTruthy(); expect(task.updated).toBe(task.created); const files = await fs.readdir(folder); expect(files).toHaveLength(1); expect(files[0]).toMatch(/\.task\.md$/); const content = await fs.readFile(`${folder}/${files[0]}`); expect(content).toContain("Fix login bug"); expect(content).toContain("status: todo"); }); it("list returns created tasks sorted by updated desc", async () => { const a = await repo.create(folder, { ...sampleTask, title: "A" }); await delay(10); const b = await repo.create(folder, { ...sampleTask, title: "B" }); const tasks = await repo.list(folder); expect(tasks).toHaveLength(2); expect(tasks[0].title).toBe("B"); expect(tasks[1].title).toBe("A"); }); it("getById returns the correct task", async () => { const created = await repo.create(folder, sampleTask); const found = await repo.getById(folder, created.id); expect(found).toEqual(created); }); it("getById returns undefined for unknown id", async () => { const found = await repo.getById(folder, "nonexistent"); expect(found).toBeUndefined(); }); it("update modifies the file and updates timestamp", async () => { const task = await repo.create(folder, sampleTask); await delay(5); const updated: Task = { ...task, title: "Fixed!", status: "done" }; await repo.update(folder, updated); const found = await repo.getById(folder, task.id); expect(found!.title).toBe("Fixed!"); expect(found!.status).toBe("done"); expect(new Date(found!.updated).getTime()).toBeGreaterThan( new Date(task.updated).getTime(), ); }); it("delete removes the task file", async () => { const task = await repo.create(folder, sampleTask); await repo.delete(folder, task.id); const tasks = await repo.list(folder); expect(tasks).toHaveLength(0); }); it("delete is a no-op for unknown id", async () => { await expect(repo.delete(folder, "nope")).resolves.toBeUndefined(); }); it("list returns empty array for non-existent folder", async () => { const tasks = await repo.list("/nonexistent"); expect(tasks).toEqual([]); }); it("filters files by extension", async () => { fs.writeFile(`${folder}/notes.txt`, "hello"); fs.writeFile(`${folder}/chore.md`, "---\nid: x\n---\n"); await repo.create(folder, sampleTask); const tasks = await repo.list(folder); expect(tasks).toHaveLength(1); }); it("skips unparseable .task.md files silently", async () => { fs.writeFile(`${folder}/corrupt.task.md`, "not valid frontmatter"); await repo.create(folder, sampleTask); const tasks = await repo.list(folder); expect(tasks).toHaveLength(1); }); }); function delay(ms: number): Promise { return new Promise((r) => setTimeout(r, ms)); }