Files
xboct-flow/tests/storage/FsTaskRepository.test.ts
T

120 lines
3.6 KiB
TypeScript

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<void> {
return new Promise((r) => setTimeout(r, ms));
}