fetch-routes.host.spec.ts 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import { Context } from '@deepseek-ai/cordis'
  2. import { describe, expect, it, vi } from 'vitest'
  3. import type { BrowserAuth } from '../src/browser-auth.ts'
  4. import { HostConnectionService } from '../src/rpc-host.ts'
  5. async function mounted(): Promise<{
  6. readonly connection: HostConnectionService
  7. readonly dispose: () => Promise<void>
  8. }> {
  9. const ctx = new Context()
  10. const fiber = ctx.plugin((pluginCtx) => {
  11. new HostConnectionService(pluginCtx, [], {} as BrowserAuth)
  12. })
  13. await fiber.await()
  14. return {
  15. connection: ctx.get('connection') as HostConnectionService,
  16. dispose: () => fiber.dispose(),
  17. }
  18. }
  19. describe('Connection exact Fetch routes', () => {
  20. it('dispatches owned methods and returns 404 for unclaimed requests', async () => {
  21. const { connection, dispose: disposeFiber } = await mounted()
  22. const route = vi.fn(async (request: Request) =>
  23. Response.json({ query: new URL(request.url).searchParams.get('sessionId') }))
  24. const dispose = connection.fetch.register({
  25. path: '/api/session.export',
  26. methods: ['GET', 'HEAD', 'POST'],
  27. requestBody: 'streaming',
  28. fetch: route,
  29. })
  30. const shared = connection.createSharedFetchHandler('/api')
  31. const response = await shared.fetch(new Request(
  32. 'http://host/api/session.export?sessionId=session-1',
  33. ))
  34. expect(shared.requestBodyMode({
  35. method: 'POST', url: new URL('http://host/api/session.export'),
  36. })).toBe('streaming')
  37. expect(shared.requestBodyMode({
  38. method: 'DELETE', url: new URL('http://host/api/session.export'),
  39. })).toBe('buffered')
  40. expect(response.status).toBe(200)
  41. expect(await response.json()).toEqual({ query: 'session-1' })
  42. expect(route).toHaveBeenCalledOnce()
  43. const post = await shared.fetch(new Request('http://host/api/session.export', { method: 'POST' }))
  44. expect(post.status).toBe(200)
  45. expect(route).toHaveBeenCalledTimes(2)
  46. await dispose()
  47. const withdrawn = await shared.fetch(new Request('http://host/api/session.export'))
  48. expect(withdrawn.status).toBe(404)
  49. await disposeFiber()
  50. })
  51. it('rejects invalid and duplicate registrations', async () => {
  52. const { connection, dispose: disposeFiber } = await mounted()
  53. const fetch = async (): Promise<Response> => new Response()
  54. expect(() => connection.fetch.register({ path: '/outside', methods: ['GET'], requestBody: 'buffered', fetch }))
  55. .toThrow('invalid exact Fetch route')
  56. expect(() => connection.fetch.register({ path: '/api/session.export', methods: [], requestBody: 'buffered', fetch }))
  57. .toThrow('declares no methods')
  58. expect(() => connection.fetch.register({
  59. path: '/api/session.export', methods: ['GET', 'GET'], fetch,
  60. requestBody: 'buffered',
  61. })).toThrow('repeats a method')
  62. const dispose = connection.fetch.register({
  63. path: '/api/session.export', methods: ['GET'], fetch,
  64. requestBody: 'buffered',
  65. })
  66. expect(() => connection.fetch.register({
  67. path: '/api/session.export', methods: ['HEAD'], fetch,
  68. requestBody: 'buffered',
  69. })).toThrow('already registered')
  70. await dispose()
  71. expect(() => connection.fetch.register({
  72. path: '/api/session.export', methods: ['HEAD'], fetch,
  73. requestBody: 'buffered',
  74. })).not.toThrow()
  75. await disposeFiber()
  76. })
  77. })