session-cache.test.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import { describe, expect, test } from "bun:test"
  2. import type {
  3. Message,
  4. Part,
  5. PermissionRequest,
  6. QuestionRequest,
  7. SessionStatus,
  8. SnapshotFileDiff,
  9. Todo,
  10. } from "@opencode-ai/sdk/v2/client"
  11. import { dropSessionCaches, pickSessionCacheEvictions } from "./session-cache"
  12. const msg = (id: string, sessionID: string) =>
  13. ({
  14. id,
  15. sessionID,
  16. role: "user",
  17. time: { created: 1 },
  18. agent: "assistant",
  19. model: { providerID: "openai", modelID: "gpt" },
  20. }) as Message
  21. const part = (id: string, sessionID: string, messageID: string) =>
  22. ({
  23. id,
  24. sessionID,
  25. messageID,
  26. type: "text",
  27. text: id,
  28. }) as Part
  29. describe("app session cache", () => {
  30. test("dropSessionCaches clears orphaned parts without message rows", () => {
  31. const store: {
  32. session_status: Record<string, SessionStatus | undefined>
  33. session_diff: Record<string, SnapshotFileDiff[] | undefined>
  34. todo: Record<string, Todo[] | undefined>
  35. message: Record<string, Message[] | undefined>
  36. part: Record<string, Part[] | undefined>
  37. permission: Record<string, PermissionRequest[] | undefined>
  38. question: Record<string, QuestionRequest[] | undefined>
  39. part_text_accum_delta: Record<string, string | undefined>
  40. } = {
  41. session_status: { ses_1: { type: "busy" } as SessionStatus },
  42. session_diff: { ses_1: [] },
  43. todo: { ses_1: [] as Todo[] },
  44. message: {},
  45. part: { msg_1: [part("prt_1", "ses_1", "msg_1")] },
  46. permission: { ses_1: [] as PermissionRequest[] },
  47. question: { ses_1: [] as QuestionRequest[] },
  48. part_text_accum_delta: { prt_1: "streamed text" },
  49. }
  50. dropSessionCaches(store, ["ses_1"])
  51. expect(store.message.ses_1).toBeUndefined()
  52. expect(store.part.msg_1).toBeUndefined()
  53. expect(store.part_text_accum_delta.prt_1).toBeUndefined()
  54. expect(store.todo.ses_1).toBeUndefined()
  55. expect(store.session_diff.ses_1).toBeUndefined()
  56. expect(store.session_status.ses_1).toBeUndefined()
  57. expect(store.permission.ses_1).toBeUndefined()
  58. expect(store.question.ses_1).toBeUndefined()
  59. })
  60. test("dropSessionCaches clears message-backed parts", () => {
  61. const m = msg("msg_1", "ses_1")
  62. const store: {
  63. session_status: Record<string, SessionStatus | undefined>
  64. session_diff: Record<string, SnapshotFileDiff[] | undefined>
  65. todo: Record<string, Todo[] | undefined>
  66. message: Record<string, Message[] | undefined>
  67. part: Record<string, Part[] | undefined>
  68. permission: Record<string, PermissionRequest[] | undefined>
  69. question: Record<string, QuestionRequest[] | undefined>
  70. part_text_accum_delta: Record<string, string | undefined>
  71. } = {
  72. session_status: {},
  73. session_diff: {},
  74. todo: {},
  75. message: { ses_1: [m] },
  76. part: { [m.id]: [part("prt_1", "ses_1", m.id)] },
  77. permission: {},
  78. question: {},
  79. part_text_accum_delta: {},
  80. }
  81. dropSessionCaches(store, ["ses_1"])
  82. expect(store.message.ses_1).toBeUndefined()
  83. expect(store.part[m.id]).toBeUndefined()
  84. })
  85. test("pickSessionCacheEvictions preserves requested sessions", () => {
  86. const seen = new Set(["ses_1", "ses_2", "ses_3"])
  87. const stale = pickSessionCacheEvictions({
  88. seen,
  89. keep: "ses_4",
  90. limit: 2,
  91. preserve: ["ses_1"],
  92. })
  93. expect(stale).toEqual(["ses_2", "ses_3"])
  94. expect([...seen]).toEqual(["ses_1", "ses_4"])
  95. })
  96. })