feat(team-mode): add team tasklist list with tests
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
/// <reference types="bun-types" />
|
||||||
|
|
||||||
|
import { expect, test } from "bun:test"
|
||||||
|
import { writeFile } from "node:fs/promises"
|
||||||
|
import path from "node:path"
|
||||||
|
|
||||||
|
import { getTasksDir, resolveBaseDir } from "../team-registry"
|
||||||
|
import { createTask } from "./store"
|
||||||
|
import { createTaskInput, createTasklistFixture } from "./test-support"
|
||||||
|
import { updateTaskStatus } from "./update"
|
||||||
|
import { listTasks } from "./list"
|
||||||
|
|
||||||
|
test("listTasks returns tasks sorted ascending and honors filters", async () => {
|
||||||
|
// given
|
||||||
|
const fixture = await createTasklistFixture()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const firstTask = await createTask(
|
||||||
|
fixture.teamRunId,
|
||||||
|
createTaskInput({ subject: "one", status: "claimed", owner: "member-a", claimedAt: Date.now() }),
|
||||||
|
fixture.config,
|
||||||
|
)
|
||||||
|
await createTask(fixture.teamRunId, createTaskInput({ subject: "two" }), fixture.config)
|
||||||
|
const thirdTask = await createTask(
|
||||||
|
fixture.teamRunId,
|
||||||
|
createTaskInput({ subject: "three", status: "claimed", owner: "member-a", claimedAt: Date.now() }),
|
||||||
|
fixture.config,
|
||||||
|
)
|
||||||
|
await updateTaskStatus(fixture.teamRunId, thirdTask.id, "in_progress", "member-a", fixture.config)
|
||||||
|
|
||||||
|
// when
|
||||||
|
const allTasks = await listTasks(fixture.teamRunId, fixture.config)
|
||||||
|
const claimedTasks = await listTasks(fixture.teamRunId, fixture.config, { status: "claimed", owner: "member-a" })
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(allTasks.map((task) => task.id)).toEqual([firstTask.id, "2", thirdTask.id])
|
||||||
|
expect(claimedTasks).toHaveLength(1)
|
||||||
|
expect(claimedTasks[0]?.id).toBe(firstTask.id)
|
||||||
|
} finally {
|
||||||
|
await fixture.cleanup()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("listTasks skips malformed task files", async () => {
|
||||||
|
// given
|
||||||
|
const fixture = await createTasklistFixture()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const validTask = await createTask(fixture.teamRunId, createTaskInput(), fixture.config)
|
||||||
|
const tasksDirectory = getTasksDir(resolveBaseDir(fixture.config), fixture.teamRunId)
|
||||||
|
await writeFile(path.join(tasksDirectory, "bad.json"), "{not-json")
|
||||||
|
await writeFile(path.join(tasksDirectory, ".highwatermark"), "1")
|
||||||
|
|
||||||
|
// when
|
||||||
|
const listedTasks = await listTasks(fixture.teamRunId, fixture.config)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(listedTasks).toHaveLength(1)
|
||||||
|
expect(listedTasks[0]?.id).toBe(validTask.id)
|
||||||
|
} finally {
|
||||||
|
await fixture.cleanup()
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import type { Dirent } from "node:fs"
|
||||||
|
import { readdir, readFile } from "node:fs/promises"
|
||||||
|
import path from "node:path"
|
||||||
|
|
||||||
|
import type { TeamModeConfig } from "../../../config/schema/team-mode"
|
||||||
|
import { log } from "../../../shared/logger"
|
||||||
|
import { getTasksDir, resolveBaseDir } from "../team-registry"
|
||||||
|
import { TaskSchema } from "../types"
|
||||||
|
import type { Task } from "../types"
|
||||||
|
|
||||||
|
type TaskListFilter = {
|
||||||
|
status?: Task["status"]
|
||||||
|
owner?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listTasks(
|
||||||
|
teamRunId: string,
|
||||||
|
config: TeamModeConfig,
|
||||||
|
filter?: TaskListFilter,
|
||||||
|
): Promise<Task[]> {
|
||||||
|
const tasksDirectory = getTasksDir(resolveBaseDir(config), teamRunId)
|
||||||
|
|
||||||
|
let entries: Dirent[]
|
||||||
|
try {
|
||||||
|
entries = await readdir(tasksDirectory, { withFileTypes: true })
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedTasks: Task[] = []
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.isDirectory() || entry.name.startsWith(".") || !entry.name.endsWith(".json")) continue
|
||||||
|
|
||||||
|
const taskPath = path.join(tasksDirectory, entry.name)
|
||||||
|
try {
|
||||||
|
const taskContent = await readFile(taskPath, "utf8")
|
||||||
|
const parsedTask = TaskSchema.safeParse(JSON.parse(taskContent))
|
||||||
|
if (!parsedTask.success) {
|
||||||
|
log("team-tasklist skipped malformed task", {
|
||||||
|
event: "team-tasklist-malformed-task",
|
||||||
|
taskPath,
|
||||||
|
issues: parsedTask.error.issues,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parsedTasks.push(parsedTask.data)
|
||||||
|
} catch (error) {
|
||||||
|
log("team-tasklist skipped malformed task", {
|
||||||
|
event: "team-tasklist-malformed-task",
|
||||||
|
taskPath,
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedTasks
|
||||||
|
.filter((task) => {
|
||||||
|
if (filter?.status !== undefined && task.status !== filter.status) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter?.owner === undefined || task.owner === filter.owner
|
||||||
|
})
|
||||||
|
.sort((leftTask, rightTask) => Number.parseInt(leftTask.id, 10) - Number.parseInt(rightTask.id, 10))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user