session-todo.test.ts 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import { describe, expect } from "bun:test"
  2. import { asc } from "drizzle-orm"
  3. import { Effect } from "effect"
  4. import { Database } from "@opencode-ai/core/database/database"
  5. import { LayerNode } from "@opencode-ai/core/effect/layer-node"
  6. import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
  7. import { EventV2 } from "@opencode-ai/core/event"
  8. import { Project } from "@opencode-ai/core/project"
  9. import { ProjectTable } from "@opencode-ai/core/project/sql"
  10. import { AbsolutePath } from "@opencode-ai/core/schema"
  11. import { SessionV2 } from "@opencode-ai/core/session"
  12. import { SessionTable, TodoTable } from "@opencode-ai/core/session/sql"
  13. import { SessionTodo } from "@opencode-ai/core/session/todo"
  14. import { testEffect } from "./lib/effect"
  15. const it = testEffect(AppNodeBuilder.build(LayerNode.group([Database.node, EventV2.node, SessionTodo.node])))
  16. const sessionID = SessionV2.ID.make("ses_todo_test")
  17. const setup = Effect.gen(function* () {
  18. const { db } = yield* Database.Service
  19. yield* db
  20. .insert(ProjectTable)
  21. .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
  22. .run()
  23. .pipe(Effect.orDie)
  24. yield* db
  25. .insert(SessionTable)
  26. .values({
  27. id: sessionID,
  28. project_id: Project.ID.global,
  29. slug: "todo",
  30. directory: "/project",
  31. title: "todo",
  32. version: "test",
  33. })
  34. .run()
  35. .pipe(Effect.orDie)
  36. })
  37. describe("SessionTodo", () => {
  38. it.effect("replaces persisted todos in order and publishes updates", () =>
  39. Effect.gen(function* () {
  40. yield* setup
  41. const { db } = yield* Database.Service
  42. const events = yield* EventV2.Service
  43. const todos = yield* SessionTodo.Service
  44. const published = new Array<EventV2.Payload>()
  45. const unsubscribe = yield* events.listen((event) =>
  46. Effect.sync(() => {
  47. if (event.type === SessionTodo.Event.Updated.type) published.push(event)
  48. }),
  49. )
  50. yield* Effect.addFinalizer(() => unsubscribe)
  51. yield* todos.update({
  52. sessionID,
  53. todos: [
  54. { content: "second", status: "pending", priority: "low" },
  55. { content: "first", status: "in_progress", priority: "high" },
  56. ],
  57. })
  58. expect(yield* todos.get(sessionID)).toEqual([
  59. { content: "second", status: "pending", priority: "low" },
  60. { content: "first", status: "in_progress", priority: "high" },
  61. ])
  62. expect(
  63. (yield* db.select().from(TodoTable).orderBy(asc(TodoTable.position)).all().pipe(Effect.orDie)).map((row) => ({
  64. content: row.content,
  65. position: row.position,
  66. })),
  67. ).toEqual([
  68. { content: "second", position: 0 },
  69. { content: "first", position: 1 },
  70. ])
  71. yield* todos.update({ sessionID, todos: [{ content: "replacement", status: "completed", priority: "medium" }] })
  72. expect(yield* todos.get(sessionID)).toEqual([{ content: "replacement", status: "completed", priority: "medium" }])
  73. yield* todos.update({ sessionID, todos: [] })
  74. expect(yield* todos.get(sessionID)).toEqual([])
  75. expect(published.map((event) => event.data)).toEqual([
  76. {
  77. sessionID,
  78. todos: [
  79. { content: "second", status: "pending", priority: "low" },
  80. { content: "first", status: "in_progress", priority: "high" },
  81. ],
  82. },
  83. { sessionID, todos: [{ content: "replacement", status: "completed", priority: "medium" }] },
  84. { sessionID, todos: [] },
  85. ])
  86. }),
  87. )
  88. })