agentManagement.test.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. import { afterEach, describe, expect, it, vi } from 'vitest'
  2. import {
  3. activateModelService,
  4. deleteModelService,
  5. getDuplexVoiceSettings,
  6. listLegacyModelServices,
  7. listModelCatalog,
  8. listModelServices,
  9. saveDuplexVoiceSettings,
  10. saveLegacyModelService,
  11. saveModelService,
  12. saveProfileDefaultModel,
  13. validateModelService,
  14. } from '@/api/agentManagement'
  15. import { setApiCsrfToken } from '@/api/client'
  16. afterEach(() => {
  17. vi.unstubAllGlobals()
  18. setApiCsrfToken('')
  19. })
  20. describe('Agent management client protocol', () => {
  21. it('uses profile-scoped admin routes and preserves omitted model secrets', async () => {
  22. const calls: Array<{ path: string; init?: RequestInit }> = []
  23. vi.stubGlobal('fetch', vi.fn(async (path: string, init?: RequestInit) => {
  24. calls.push({ path, init })
  25. return Response.json({ endpoints: [], current: {}, ok: true, reachable: true, message: '', models: [] })
  26. }))
  27. setApiCsrfToken('csrf-agent-management')
  28. const input = { name: 'Local', base_url: 'http://127.0.0.1:9000/v1', model: 'local-model', discover_models: true, make_default: false }
  29. await listModelServices('worker / 中文')
  30. await listModelCatalog('worker / 中文')
  31. await listLegacyModelServices('worker / 中文')
  32. await saveModelService('worker / 中文', input)
  33. await saveLegacyModelService('worker / 中文', 'custom:tingly', input)
  34. await saveProfileDefaultModel('worker / 中文', 'custom:tingly', 'omni')
  35. await validateModelService(input)
  36. await activateModelService('worker / 中文', 'local/model')
  37. await deleteModelService('worker / 中文', 'local/model')
  38. expect(calls.map(call => call.path)).toEqual([
  39. '/api/app/admin/model-services?profile=worker%20%2F%20%E4%B8%AD%E6%96%87',
  40. '/api/app/models?profile=worker%20%2F%20%E4%B8%AD%E6%96%87',
  41. '/api/app/admin/legacy-model-services?profile=worker%20%2F%20%E4%B8%AD%E6%96%87',
  42. '/api/app/admin/model-services?profile=worker%20%2F%20%E4%B8%AD%E6%96%87',
  43. '/api/app/admin/legacy-model-services/custom%3Atingly?profile=worker%20%2F%20%E4%B8%AD%E6%96%87',
  44. '/api/app/admin/profiles/worker%20%2F%20%E4%B8%AD%E6%96%87/model',
  45. '/api/app/admin/model-services/validate',
  46. '/api/app/admin/model-services/local%2Fmodel/activate?profile=worker%20%2F%20%E4%B8%AD%E6%96%87',
  47. '/api/app/admin/model-services/local%2Fmodel?profile=worker%20%2F%20%E4%B8%AD%E6%96%87',
  48. ])
  49. expect(JSON.parse(String(calls[3]!.init?.body))).not.toHaveProperty('api_key')
  50. expect(JSON.parse(String(calls[5]!.init?.body))).toEqual({ provider: 'custom:tingly', model: 'omni' })
  51. expect(new Headers(calls[3]!.init?.headers).get('X-CSRF-Token')).toBe('csrf-agent-management')
  52. expect(calls[8]!.init?.method).toBe('DELETE')
  53. })
  54. it('never includes an unchanged duplex API key', async () => {
  55. const calls: Array<{ path: string; init?: RequestInit }> = []
  56. vi.stubGlobal('fetch', vi.fn(async (path: string, init?: RequestInit) => {
  57. calls.push({ path, init })
  58. return Response.json({ hasApiKey: true, voices: [{ id: 'voice-1', name: 'Voice' }], currentVoiceId: 'voice-1', updatedAt: 1 })
  59. }))
  60. setApiCsrfToken('csrf-voice')
  61. await getDuplexVoiceSettings()
  62. await saveDuplexVoiceSettings({ voices: [{ id: 'voice-1', name: 'Voice' }], currentVoiceId: 'voice-1' })
  63. expect(calls.map(call => call.path)).toEqual(['/api/app/admin/duplex-voice', '/api/app/admin/duplex-voice'])
  64. expect(JSON.parse(String(calls[1]!.init?.body))).toEqual({ voices: [{ id: 'voice-1', name: 'Voice' }], currentVoiceId: 'voice-1' })
  65. })
  66. })