session-todo.test.ts 3.5 KB

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