server-session.test.ts 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import { describe, expect, test } from "bun:test"
  2. import type { OpencodeClient, Session } from "@opencode-ai/sdk/v2/client"
  3. import { createServerSession } from "./server-session"
  4. const session = (id: string, parentID?: string): Session => ({
  5. id,
  6. slug: id,
  7. projectID: "project",
  8. directory: "/repo",
  9. title: id,
  10. version: "1",
  11. parentID,
  12. time: { created: 1, updated: 1 },
  13. })
  14. function setup(sessions: Record<string, Session>) {
  15. const get: unknown[] = []
  16. const messages: unknown[] = []
  17. const client = {
  18. session: {
  19. get: async (input: unknown) => {
  20. get.push(input)
  21. const id = (input as { sessionID: string }).sessionID
  22. return { data: sessions[id] }
  23. },
  24. messages: async (input: unknown) => {
  25. messages.push(input)
  26. return { data: [], response: { headers: new Headers() } }
  27. },
  28. diff: async () => ({ data: [] }),
  29. todo: async () => ({ data: [] }),
  30. },
  31. } as unknown as OpencodeClient
  32. return { get, messages, store: createServerSession(client) }
  33. }
  34. describe("server session", () => {
  35. test("resolves lineage by session ID without directory", async () => {
  36. const ctx = setup({ child: session("child", "root"), root: session("root") })
  37. const result = await ctx.store.lineage.resolve("child")
  38. expect(result.root.id).toBe("root")
  39. expect(ctx.get).toEqual([{ sessionID: "child" }, { sessionID: "root" }])
  40. expect(ctx.store.lineage.peek("child")).toEqual(result)
  41. })
  42. test("loads session content through the server client", async () => {
  43. const ctx = setup({ root: session("root") })
  44. await ctx.store.sync("root")
  45. expect(ctx.get).toEqual([{ sessionID: "root" }])
  46. expect(ctx.messages).toEqual([{ sessionID: "root", limit: 2, before: undefined }])
  47. expect(ctx.store.data.message.root).toEqual([])
  48. })
  49. test("applies events without a directory store", () => {
  50. const ctx = setup({})
  51. ctx.store.apply({ type: "session.created", properties: { info: session("root") } })
  52. ctx.store.apply({ type: "session.status", properties: { sessionID: "root", status: { type: "busy" } } })
  53. expect(ctx.store.get("root")?.directory).toBe("/repo")
  54. expect(ctx.store.data.session_working("root")).toBe(true)
  55. })
  56. test("preserves pinned session content under server-wide cache pressure", () => {
  57. const ctx = setup({})
  58. ctx.store.pin("active")
  59. ctx.store.optimistic.add({
  60. sessionID: "active",
  61. message: {
  62. id: "message",
  63. sessionID: "active",
  64. role: "assistant",
  65. time: { created: 1 },
  66. parentID: "parent",
  67. modelID: "model",
  68. providerID: "provider",
  69. mode: "build",
  70. agent: "agent",
  71. path: { cwd: "/repo", root: "/repo" },
  72. cost: 0,
  73. tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
  74. },
  75. parts: [],
  76. })
  77. for (let index = 0; index < 50; index++) {
  78. ctx.store.apply({
  79. type: "session.status",
  80. properties: { sessionID: `session-${index}`, status: { type: "busy" } },
  81. })
  82. }
  83. expect(ctx.store.data.message.active?.map((message) => message.id)).toEqual(["message"])
  84. })
  85. })