agentManagement.test.ts 3.2 KB

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