- Add optional team_name parameter to task_create - Route to team-namespaced storage when team_name provided - Preserve existing behavior when team_name absent - Add tests for both team and regular task creation - Part of Task 12 (1/4 files complete)
339 lines
9.6 KiB
TypeScript
339 lines
9.6 KiB
TypeScript
import { describe, test, expect, beforeEach, afterEach } from "bun:test"
|
|
import { existsSync, rmSync, mkdirSync } from "fs"
|
|
import { join } from "path"
|
|
import type { TaskObject } from "./types"
|
|
import { createTaskCreateTool } from "./task-create"
|
|
|
|
const TEST_STORAGE = ".test-task-create-tool"
|
|
const TEST_DIR = join(process.cwd(), TEST_STORAGE)
|
|
const TEST_CONFIG = {
|
|
sisyphus: {
|
|
tasks: {
|
|
storage_path: TEST_STORAGE,
|
|
claude_code_compat: true,
|
|
},
|
|
},
|
|
}
|
|
const TEST_SESSION_ID = "test-session-123"
|
|
const TEST_ABORT_CONTROLLER = new AbortController()
|
|
const TEST_CONTEXT = {
|
|
sessionID: TEST_SESSION_ID,
|
|
messageID: "test-message-123",
|
|
agent: "test-agent",
|
|
abort: TEST_ABORT_CONTROLLER.signal,
|
|
}
|
|
|
|
describe("task_create tool", () => {
|
|
let tool: ReturnType<typeof createTaskCreateTool>
|
|
|
|
beforeEach(() => {
|
|
if (existsSync(TEST_STORAGE)) {
|
|
rmSync(TEST_STORAGE, { recursive: true, force: true })
|
|
}
|
|
mkdirSync(TEST_DIR, { recursive: true })
|
|
tool = createTaskCreateTool(TEST_CONFIG)
|
|
})
|
|
|
|
afterEach(() => {
|
|
if (existsSync(TEST_STORAGE)) {
|
|
rmSync(TEST_STORAGE, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
describe("create action", () => {
|
|
test("creates task with required subject field", async () => {
|
|
//#given
|
|
const args = {
|
|
subject: "Implement authentication",
|
|
}
|
|
|
|
//#when
|
|
const resultStr = await tool.execute(args, TEST_CONTEXT)
|
|
const result = JSON.parse(resultStr)
|
|
|
|
//#then
|
|
expect(result).toHaveProperty("task")
|
|
expect(result.task).toHaveProperty("id")
|
|
expect(result.task.subject).toBe("Implement authentication")
|
|
})
|
|
|
|
test("auto-generates T-{uuid} format ID", async () => {
|
|
//#given
|
|
const args = {
|
|
subject: "Test task",
|
|
}
|
|
|
|
//#when
|
|
const resultStr = await tool.execute(args, TEST_CONTEXT)
|
|
const result = JSON.parse(resultStr)
|
|
|
|
//#then
|
|
expect(result.task.id).toMatch(/^T-[a-f0-9-]+$/)
|
|
})
|
|
|
|
test("auto-records threadID from session context", async () => {
|
|
//#given
|
|
const args = {
|
|
subject: "Test task",
|
|
}
|
|
|
|
//#when
|
|
const resultStr = await tool.execute(args, TEST_CONTEXT)
|
|
const result = JSON.parse(resultStr)
|
|
const taskId = result.task.id
|
|
|
|
//#then
|
|
const taskFile = join(TEST_DIR, `${taskId}.json`)
|
|
expect(existsSync(taskFile)).toBe(true)
|
|
const taskContent = JSON.parse(await Bun.file(taskFile).text())
|
|
expect(taskContent.threadID).toBe(TEST_SESSION_ID)
|
|
})
|
|
|
|
test("sets default status to pending", async () => {
|
|
//#given
|
|
const args = {
|
|
subject: "Test task",
|
|
}
|
|
|
|
//#when
|
|
const resultStr = await tool.execute(args, TEST_CONTEXT)
|
|
const result = JSON.parse(resultStr)
|
|
const taskId = result.task.id
|
|
|
|
//#then
|
|
const taskFile = join(TEST_DIR, `${taskId}.json`)
|
|
const taskContent = JSON.parse(await Bun.file(taskFile).text())
|
|
expect(taskContent.status).toBe("pending")
|
|
})
|
|
|
|
test("sets default blocks and blockedBy to empty arrays", async () => {
|
|
//#given
|
|
const args = {
|
|
subject: "Test task",
|
|
}
|
|
|
|
//#when
|
|
const resultStr = await tool.execute(args, TEST_CONTEXT)
|
|
const result = JSON.parse(resultStr)
|
|
const taskId = result.task.id
|
|
|
|
//#then
|
|
const taskFile = join(TEST_DIR, `${taskId}.json`)
|
|
const taskContent = JSON.parse(await Bun.file(taskFile).text())
|
|
expect(taskContent.blocks).toEqual([])
|
|
expect(taskContent.blockedBy).toEqual([])
|
|
})
|
|
|
|
test("accepts optional description", async () => {
|
|
//#given
|
|
const args = {
|
|
subject: "Test task",
|
|
description: "This is a test description",
|
|
}
|
|
|
|
//#when
|
|
const resultStr = await tool.execute(args, TEST_CONTEXT)
|
|
const result = JSON.parse(resultStr)
|
|
const taskId = result.task.id
|
|
|
|
//#then
|
|
const taskFile = join(TEST_DIR, `${taskId}.json`)
|
|
const taskContent = JSON.parse(await Bun.file(taskFile).text())
|
|
expect(taskContent.description).toBe("This is a test description")
|
|
})
|
|
|
|
test("accepts optional activeForm", async () => {
|
|
//#given
|
|
const args = {
|
|
subject: "Test task",
|
|
activeForm: "Implementing authentication",
|
|
}
|
|
|
|
//#when
|
|
const resultStr = await tool.execute(args, TEST_CONTEXT)
|
|
const result = JSON.parse(resultStr)
|
|
const taskId = result.task.id
|
|
|
|
//#then
|
|
const taskFile = join(TEST_DIR, `${taskId}.json`)
|
|
const taskContent = JSON.parse(await Bun.file(taskFile).text())
|
|
expect(taskContent.activeForm).toBe("Implementing authentication")
|
|
})
|
|
|
|
test("accepts optional metadata", async () => {
|
|
//#given
|
|
const args = {
|
|
subject: "Test task",
|
|
metadata: { priority: "high", tags: ["urgent"] },
|
|
}
|
|
|
|
//#when
|
|
const resultStr = await tool.execute(args, TEST_CONTEXT)
|
|
const result = JSON.parse(resultStr)
|
|
const taskId = result.task.id
|
|
|
|
//#then
|
|
const taskFile = join(TEST_DIR, `${taskId}.json`)
|
|
const taskContent = JSON.parse(await Bun.file(taskFile).text())
|
|
expect(taskContent.metadata).toEqual({ priority: "high", tags: ["urgent"] })
|
|
})
|
|
|
|
test("accepts optional blockedBy array", async () => {
|
|
//#given
|
|
const args = {
|
|
subject: "Test task",
|
|
blockedBy: ["T-123", "T-456"],
|
|
}
|
|
|
|
//#when
|
|
const resultStr = await tool.execute(args, TEST_CONTEXT)
|
|
const result = JSON.parse(resultStr)
|
|
const taskId = result.task.id
|
|
|
|
//#then
|
|
const taskFile = join(TEST_DIR, `${taskId}.json`)
|
|
const taskContent = JSON.parse(await Bun.file(taskFile).text())
|
|
expect(taskContent.blockedBy).toEqual(["T-123", "T-456"])
|
|
})
|
|
|
|
test("accepts optional blocks array", async () => {
|
|
//#given
|
|
const args = {
|
|
subject: "Test task",
|
|
blocks: ["T-789", "T-101"],
|
|
}
|
|
|
|
//#when
|
|
const resultStr = await tool.execute(args, TEST_CONTEXT)
|
|
const result = JSON.parse(resultStr)
|
|
const taskId = result.task.id
|
|
|
|
//#then
|
|
const taskFile = join(TEST_DIR, `${taskId}.json`)
|
|
const taskContent = JSON.parse(await Bun.file(taskFile).text())
|
|
expect(taskContent.blocks).toEqual(["T-789", "T-101"])
|
|
})
|
|
|
|
test("accepts optional repoURL", async () => {
|
|
//#given
|
|
const args = {
|
|
subject: "Test task",
|
|
repoURL: "https://github.com/example/repo",
|
|
}
|
|
|
|
//#when
|
|
const resultStr = await tool.execute(args, TEST_CONTEXT)
|
|
const result = JSON.parse(resultStr)
|
|
const taskId = result.task.id
|
|
|
|
//#then
|
|
const taskFile = join(TEST_DIR, `${taskId}.json`)
|
|
const taskContent = JSON.parse(await Bun.file(taskFile).text())
|
|
expect(taskContent.repoURL).toBe("https://github.com/example/repo")
|
|
})
|
|
|
|
test("accepts optional parentID", async () => {
|
|
//#given
|
|
const args = {
|
|
subject: "Test task",
|
|
parentID: "T-parent-123",
|
|
}
|
|
|
|
//#when
|
|
const resultStr = await tool.execute(args, TEST_CONTEXT)
|
|
const result = JSON.parse(resultStr)
|
|
const taskId = result.task.id
|
|
|
|
//#then
|
|
const taskFile = join(TEST_DIR, `${taskId}.json`)
|
|
const taskContent = JSON.parse(await Bun.file(taskFile).text())
|
|
expect(taskContent.parentID).toBe("T-parent-123")
|
|
})
|
|
|
|
test("returns minimal response with id and subject", async () => {
|
|
//#given
|
|
const args = {
|
|
subject: "Test task",
|
|
}
|
|
|
|
//#when
|
|
const resultStr = await tool.execute(args, TEST_CONTEXT)
|
|
const result = JSON.parse(resultStr)
|
|
|
|
//#then
|
|
expect(result.task).toHaveProperty("id")
|
|
expect(result.task).toHaveProperty("subject")
|
|
expect(result.task.subject).toBe("Test task")
|
|
})
|
|
|
|
test("rejects missing subject", async () => {
|
|
//#given
|
|
const args = {}
|
|
|
|
//#when
|
|
const resultStr = await tool.execute(args, TEST_CONTEXT)
|
|
const result = JSON.parse(resultStr)
|
|
|
|
//#then
|
|
expect(result).toHaveProperty("error")
|
|
})
|
|
|
|
test("writes task to file storage atomically", async () => {
|
|
//#given
|
|
const args = {
|
|
subject: "Test task",
|
|
description: "Test description",
|
|
}
|
|
|
|
//#when
|
|
const resultStr = await tool.execute(args, TEST_CONTEXT)
|
|
const result = JSON.parse(resultStr)
|
|
const taskId = result.task.id
|
|
|
|
//#then
|
|
const taskFile = join(TEST_DIR, `${taskId}.json`)
|
|
expect(existsSync(taskFile)).toBe(true)
|
|
const taskContent = JSON.parse(await Bun.file(taskFile).text())
|
|
expect(taskContent.id).toBe(taskId)
|
|
expect(taskContent.subject).toBe("Test task")
|
|
expect(taskContent.description).toBe("Test description")
|
|
})
|
|
|
|
test("creates task in team namespace when team_name provided", async () => {
|
|
//#given
|
|
const args = {
|
|
subject: "Team task",
|
|
team_name: "test-team",
|
|
}
|
|
|
|
//#when
|
|
const resultStr = await tool.execute(args, TEST_CONTEXT)
|
|
const result = JSON.parse(resultStr)
|
|
|
|
//#then
|
|
expect(result).toHaveProperty("task")
|
|
expect(result.task).toHaveProperty("id")
|
|
expect(result.task.subject).toBe("Team task")
|
|
})
|
|
|
|
test("creates task in regular storage when no team_name", async () => {
|
|
//#given
|
|
const args = {
|
|
subject: "Regular task",
|
|
}
|
|
|
|
//#when
|
|
const resultStr = await tool.execute(args, TEST_CONTEXT)
|
|
const result = JSON.parse(resultStr)
|
|
|
|
//#then
|
|
expect(result).toHaveProperty("task")
|
|
expect(result.task.subject).toBe("Regular task")
|
|
// Verify it's in regular storage
|
|
const taskId = result.task.id
|
|
const taskFile = join(TEST_DIR, `${taskId}.json`)
|
|
expect(existsSync(taskFile)).toBe(true)
|
|
})
|
|
})
|
|
})
|