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