share.test.ts 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. import { describe, expect, test, afterAll } from "bun:test"
  2. import { Share } from "../../src/core/share"
  3. import { Storage } from "../../src/core/storage"
  4. import { Identifier } from "@opencode-ai/util/identifier"
  5. describe.concurrent("core.share", () => {
  6. test("should create a share", async () => {
  7. const sessionID = Identifier.descending()
  8. const share = await Share.create({ sessionID })
  9. expect(share.sessionID).toBe(sessionID)
  10. expect(share.secret).toBeDefined()
  11. await Share.remove({ id: share.id, secret: share.secret })
  12. })
  13. test("should sync data to a share", async () => {
  14. const sessionID = Identifier.descending()
  15. const share = await Share.create({ sessionID })
  16. const data: Share.Data[] = [
  17. {
  18. type: "part",
  19. data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
  20. },
  21. ]
  22. await Share.sync({
  23. share: { id: share.id, secret: share.secret },
  24. data,
  25. })
  26. const events = await Storage.list({ prefix: ["share_event", share.id] })
  27. expect(events.length).toBe(1)
  28. await Share.remove({ id: share.id, secret: share.secret })
  29. })
  30. test("should sync multiple batches of data", async () => {
  31. const sessionID = Identifier.descending()
  32. const share = await Share.create({ sessionID })
  33. const data1: Share.Data[] = [
  34. {
  35. type: "part",
  36. data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
  37. },
  38. ]
  39. const data2: Share.Data[] = [
  40. {
  41. type: "part",
  42. data: { id: "part2", sessionID, messageID: "msg1", type: "text", text: "World" },
  43. },
  44. ]
  45. await Share.sync({
  46. share: { id: share.id, secret: share.secret },
  47. data: data1,
  48. })
  49. await Share.sync({
  50. share: { id: share.id, secret: share.secret },
  51. data: data2,
  52. })
  53. const events = await Storage.list({ prefix: ["share_event", share.id] })
  54. expect(events.length).toBe(2)
  55. await Share.remove({ id: share.id, secret: share.secret })
  56. })
  57. test("should retrieve synced data", async () => {
  58. const sessionID = Identifier.descending()
  59. const share = await Share.create({ sessionID })
  60. const data: Share.Data[] = [
  61. {
  62. type: "part",
  63. data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
  64. },
  65. {
  66. type: "part",
  67. data: { id: "part2", sessionID, messageID: "msg1", type: "text", text: "World" },
  68. },
  69. ]
  70. await Share.sync({
  71. share: { id: share.id, secret: share.secret },
  72. data,
  73. })
  74. const result = await Share.data(share.id)
  75. expect(result.length).toBe(2)
  76. expect(result[0].type).toBe("part")
  77. expect(result[1].type).toBe("part")
  78. await Share.remove({ id: share.id, secret: share.secret })
  79. })
  80. test("should retrieve data from multiple syncs", async () => {
  81. const sessionID = Identifier.descending()
  82. const share = await Share.create({ sessionID })
  83. const data1: Share.Data[] = [
  84. {
  85. type: "part",
  86. data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
  87. },
  88. ]
  89. const data2: Share.Data[] = [
  90. {
  91. type: "part",
  92. data: { id: "part2", sessionID, messageID: "msg2", type: "text", text: "World" },
  93. },
  94. ]
  95. const data3: Share.Data[] = [
  96. { type: "part", data: { id: "part3", sessionID, messageID: "msg3", type: "text", text: "!" } },
  97. ]
  98. await Share.sync({
  99. share: { id: share.id, secret: share.secret },
  100. data: data1,
  101. })
  102. await Share.sync({
  103. share: { id: share.id, secret: share.secret },
  104. data: data2,
  105. })
  106. await Share.sync({
  107. share: { id: share.id, secret: share.secret },
  108. data: data3,
  109. })
  110. const result = await Share.data(share.id)
  111. expect(result.length).toBe(3)
  112. const parts = result.filter((d) => d.type === "part")
  113. expect(parts.length).toBe(3)
  114. await Share.remove({ id: share.id, secret: share.secret })
  115. })
  116. test("should return latest data when syncing duplicate parts", async () => {
  117. const sessionID = Identifier.descending()
  118. const share = await Share.create({ sessionID })
  119. const data1: Share.Data[] = [
  120. {
  121. type: "part",
  122. data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
  123. },
  124. ]
  125. const data2: Share.Data[] = [
  126. {
  127. type: "part",
  128. data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello Updated" },
  129. },
  130. ]
  131. await Share.sync({
  132. share: { id: share.id, secret: share.secret },
  133. data: data1,
  134. })
  135. await Share.sync({
  136. share: { id: share.id, secret: share.secret },
  137. data: data2,
  138. })
  139. const result = await Share.data(share.id)
  140. expect(result.length).toBe(1)
  141. const [first] = result
  142. expect(first.type).toBe("part")
  143. expect(first.type === "part" && first.data.type === "text" && first.data.text).toBe("Hello Updated")
  144. await Share.remove({ id: share.id, secret: share.secret })
  145. })
  146. test("should return empty array for share with no data", async () => {
  147. const sessionID = Identifier.descending()
  148. const share = await Share.create({ sessionID })
  149. const result = await Share.data(share.id)
  150. expect(result).toEqual([])
  151. await Share.remove({ id: share.id, secret: share.secret })
  152. })
  153. test("should throw error for invalid secret", async () => {
  154. const sessionID = Identifier.descending()
  155. const share = await Share.create({ sessionID })
  156. const data: Share.Data[] = [
  157. {
  158. type: "part",
  159. data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Test" },
  160. },
  161. ]
  162. expect(async () => {
  163. await Share.sync({
  164. share: { id: share.id, secret: "invalid-secret" },
  165. data,
  166. })
  167. }).toThrow()
  168. await Share.remove({ id: share.id, secret: share.secret })
  169. })
  170. test("should throw error for non-existent share", async () => {
  171. const sessionID = Identifier.descending()
  172. const data: Share.Data[] = [
  173. {
  174. type: "part",
  175. data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Test" },
  176. },
  177. ]
  178. expect(async () => {
  179. await Share.sync({
  180. share: { id: "non-existent-id", secret: "some-secret" },
  181. data,
  182. })
  183. }).toThrow()
  184. })
  185. test("should handle different data types", async () => {
  186. const sessionID = Identifier.descending()
  187. const share = await Share.create({ sessionID })
  188. const data: Share.Data[] = [
  189. { type: "session", data: { id: sessionID, status: "running" } as any },
  190. { type: "message", data: { id: "msg1", sessionID } as any },
  191. {
  192. type: "part",
  193. data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
  194. },
  195. ]
  196. await Share.sync({
  197. share: { id: share.id, secret: share.secret },
  198. data,
  199. })
  200. const result = await Share.data(share.id)
  201. expect(result.length).toBe(3)
  202. expect(result.some((d) => d.type === "session")).toBe(true)
  203. expect(result.some((d) => d.type === "message")).toBe(true)
  204. expect(result.some((d) => d.type === "part")).toBe(true)
  205. await Share.remove({ id: share.id, secret: share.secret })
  206. })
  207. })