settings-document-store.spec.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import { describe, expect, it, vi } from 'vitest'
  2. import type { RpcResponse } from '@deepseek-ai/dsh-client-connection/client'
  3. import { SettingsDocumentStore } from '../src/client/settings-document-store.ts'
  4. function response(hasDocument = false): RpcResponse<{
  5. writable: boolean
  6. hasDocument: boolean
  7. namespaces: []
  8. }> {
  9. return {
  10. rpcId: 'settings-document' as never,
  11. result: {
  12. ok: true,
  13. value: { writable: true, hasDocument, namespaces: [] },
  14. },
  15. }
  16. }
  17. function opened(): RpcResponse<{ opened: true }> {
  18. return {
  19. rpcId: 'settings-open' as never,
  20. result: { ok: true, value: { opened: true } },
  21. }
  22. }
  23. function describeFailed(message: string): RpcResponse<never> {
  24. return {
  25. rpcId: 'settings-document-failed' as never,
  26. result: { ok: false, error: { code: 'internal', message, details: {} } },
  27. }
  28. }
  29. describe('SettingsDocumentStore', () => {
  30. it('loads provider metadata and asks the settings domain to open its document', async () => {
  31. const describe = vi.fn(() => Promise.resolve(response(true)))
  32. const openDocument = vi.fn(() => Promise.resolve(opened()))
  33. const controller = new SettingsDocumentStore({ settings: { describe, openDocument } } as never)
  34. await controller.load()
  35. expect(controller.store.getSnapshot()).toEqual({
  36. status: 'ready', opening: false, error: null,
  37. })
  38. await controller.open()
  39. expect(openDocument).toHaveBeenCalledWith({})
  40. })
  41. it('marks absent or failed metadata unavailable without opening anything', async () => {
  42. const openDocument = vi.fn(() => Promise.resolve(opened()))
  43. const absent = new SettingsDocumentStore({
  44. settings: { describe: () => Promise.resolve(response()), openDocument },
  45. } as never)
  46. await absent.load()
  47. await absent.open()
  48. expect(absent.store.getSnapshot().status).toBe('unavailable')
  49. expect(openDocument).not.toHaveBeenCalled()
  50. const failed = new SettingsDocumentStore({
  51. settings: { describe: () => Promise.reject(new Error('offline')), openDocument },
  52. } as never)
  53. await failed.load()
  54. expect(failed.store.getSnapshot()).toMatchObject({ status: 'unavailable', error: 'offline' })
  55. const rejected = new SettingsDocumentStore({
  56. settings: { describe: () => Promise.resolve(describeFailed('provider failed')), openDocument },
  57. } as never)
  58. await rejected.load()
  59. expect(rejected.store.getSnapshot()).toMatchObject({
  60. status: 'unavailable', error: 'provider failed',
  61. })
  62. })
  63. it('collapses concurrent open gestures and recovers after a failure', async () => {
  64. let resolveOpen!: (response: RpcResponse<{ opened: true }>) => void
  65. const openDocument = vi.fn(() => new Promise<RpcResponse<{ opened: true }>>((resolve) => { resolveOpen = resolve }))
  66. const controller = new SettingsDocumentStore({
  67. settings: { describe: () => Promise.resolve(response(true)), openDocument },
  68. } as never)
  69. await controller.load()
  70. const first = controller.open()
  71. const second = controller.open()
  72. expect(openDocument).toHaveBeenCalledOnce()
  73. resolveOpen({
  74. rpcId: 'settings-open-failed' as never,
  75. result: { ok: false, error: { code: 'internal', message: 'no default editor', details: {} } },
  76. })
  77. await Promise.all([first, second])
  78. expect(controller.store.getSnapshot()).toMatchObject({
  79. status: 'ready', opening: false, error: 'no default editor',
  80. })
  81. })
  82. it('ignores stale metadata completions and reports non-Error native failures', async () => {
  83. let resolveFirst!: (value: ReturnType<typeof response>) => void
  84. const first = new Promise<ReturnType<typeof response>>((resolve) => { resolveFirst = resolve })
  85. const describe = vi.fn()
  86. .mockReturnValueOnce(first)
  87. .mockResolvedValueOnce(response(true))
  88. let rejectOpen!: (reason?: unknown) => void
  89. const controller = new SettingsDocumentStore({
  90. settings: {
  91. describe,
  92. openDocument: () => new Promise((_, reject) => { rejectOpen = reject }),
  93. },
  94. } as never)
  95. const stale = controller.load()
  96. await controller.load()
  97. resolveFirst(response())
  98. await stale
  99. expect(controller.store.getSnapshot().status).toBe('ready')
  100. const opening = controller.open()
  101. rejectOpen('native unavailable')
  102. await opening
  103. expect(controller.store.getSnapshot()).toMatchObject({
  104. status: 'ready', opening: false, error: 'native unavailable',
  105. })
  106. let rejectFirst!: (error: Error) => void
  107. const rejectedFirst = new Promise<ReturnType<typeof response>>((_, reject) => { rejectFirst = reject })
  108. const caught = new SettingsDocumentStore({
  109. settings: {
  110. describe: vi.fn()
  111. .mockReturnValueOnce(rejectedFirst)
  112. .mockResolvedValueOnce(response(true)),
  113. openDocument: vi.fn(),
  114. },
  115. } as never)
  116. const staleRejection = caught.load()
  117. await caught.load()
  118. rejectFirst(new Error('stale offline'))
  119. await staleRejection
  120. expect(caught.store.getSnapshot()).toMatchObject({ status: 'ready', error: null })
  121. })
  122. })