session-open-workspace-path.host.spec.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import { Context } from '@deepseek-ai/cordis'
  2. import AgentRegistry from '@deepseek-ai/dsh-agent'
  3. import SessionStore from '@deepseek-ai/dsh-session'
  4. import { describe, expect, it, vi } from 'vitest'
  5. import {
  6. createSessionTestController,
  7. createSessionTestRemote,
  8. } from './test-remote.ts'
  9. async function context(): Promise<Context> {
  10. const ctx = new Context()
  11. await ctx.plugin(SessionStore)
  12. await ctx.plugin(AgentRegistry)
  13. return ctx
  14. }
  15. describe('session/openWorkspacePath', () => {
  16. it('hands a Client-resolved workspace path to the Host opener unchanged', async () => {
  17. const ctx = await context()
  18. const openPath = vi.fn((_path: string, _signal: AbortSignal) => Promise.resolve())
  19. const remote = createSessionTestRemote(ctx, {
  20. defaultModelSelection: () => ({ provider: 'p', model: 'm' }),
  21. cwd: '/default',
  22. openPath,
  23. })
  24. const signal = new AbortController().signal
  25. await expect(remote.openWorkspacePath({ path: '/workspace/project/src/a.ts' }, signal))
  26. .resolves.toEqual({ ok: true, value: { opened: true } })
  27. expect(openPath).toHaveBeenCalledWith('/workspace/project/src/a.ts', signal)
  28. expect(ctx.agents.list()).toEqual([])
  29. })
  30. it('preserves relative and absolute Host-resolvable paths', async () => {
  31. const ctx = await context()
  32. const openPath = vi.fn((_path: string, _signal: AbortSignal) => Promise.resolve())
  33. const remote = createSessionTestRemote(ctx, {
  34. defaultModelSelection: () => ({ provider: 'p', model: 'm' }),
  35. cwd: '/default',
  36. openPath,
  37. })
  38. await remote.openWorkspacePath({ path: '/tmp/result.html' })
  39. await remote.openWorkspacePath({ path: 'result.html' })
  40. expect(openPath.mock.calls.map(call => call[0])).toEqual(['/tmp/result.html', 'result.html'])
  41. })
  42. it('rejects empty paths before opening anything', async () => {
  43. const ctx = await context()
  44. const openPath = vi.fn((_path: string, _signal: AbortSignal) => Promise.resolve())
  45. const remote = createSessionTestRemote(ctx, {
  46. defaultModelSelection: () => ({ provider: 'p', model: 'm' }),
  47. cwd: '/default',
  48. openPath,
  49. })
  50. await expect(remote.openWorkspacePath({ path: '' }))
  51. .resolves.toMatchObject({ ok: false, error: { code: 'bad-request' } })
  52. expect(openPath).not.toHaveBeenCalled()
  53. })
  54. it('preserves native opener failure and cancellation results', async () => {
  55. const ctx = await context()
  56. const openPath = vi.fn((_path: string, _signal: AbortSignal) =>
  57. Promise.reject(new Error('desktop unavailable')))
  58. const remote = createSessionTestRemote(ctx, {
  59. defaultModelSelection: () => ({ provider: 'p', model: 'm' }),
  60. cwd: '/default',
  61. openPath,
  62. })
  63. await expect(remote.openWorkspacePath({ path: 'result.html' }))
  64. .resolves.toMatchObject({
  65. ok: false,
  66. error: { code: 'internal', message: 'path open failed: desktop unavailable' },
  67. })
  68. const aborted = new AbortController()
  69. aborted.abort(new Error('cancelled'))
  70. await expect(remote.openWorkspacePath({ path: 'result.html' }, aborted.signal))
  71. .resolves.toMatchObject({ ok: false, error: { code: 'cancelled' } })
  72. })
  73. it('classifies opener cancellation and non-Error failures', async () => {
  74. const ctx = await context()
  75. const aborted = new AbortController()
  76. const openPath = vi.fn()
  77. .mockImplementationOnce(async () => {
  78. aborted.abort(new Error('cancelled'))
  79. throw new Error('opening stopped')
  80. })
  81. .mockRejectedValueOnce('desktop unavailable')
  82. const controller = createSessionTestController(ctx, {
  83. defaultModelSelection: () => ({ provider: 'p', model: 'm' }),
  84. cwd: '/default',
  85. openPath,
  86. })
  87. await expect(controller.openWorkspacePath({ path: 'first.html' }, aborted.signal))
  88. .rejects.toMatchObject({ failure: { code: 'cancelled' } })
  89. await expect(controller.openWorkspacePath({
  90. path: 'second.html',
  91. }, new AbortController().signal)).rejects.toMatchObject({
  92. failure: { code: 'internal', message: 'path open failed: desktop unavailable' },
  93. })
  94. })
  95. })