hermesBridgePanel.test.ts 6.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. import {flushPromises,mount} from '@vue/test-utils'
  2. import {afterEach,beforeEach,expect,it,vi} from 'vitest'
  3. import HermesBridgePanel from '@/components/app/HermesBridgePanel.vue'
  4. import {getHermesBridgeStatus,installHermesBridge,restartHermesDashboard} from '@/api/hermesBridge'
  5. import type {HermesBridgeStatus} from '@shared/hermesBridge'
  6. vi.mock('@/api/hermesBridge',()=>({getHermesBridgeStatus:vi.fn(),installHermesBridge:vi.fn(),restartHermesDashboard:vi.fn()}))
  7. const snapshot=():HermesBridgeStatus=>({endpoint:'http://127.0.0.1:9119',local:true,bundledVersion:'1.2.0',checkedAt:1,profiles:[
  8. {profile:'default',state:'outdated',message:'需要更新',canInstall:true,installedVersion:'1.1.0'},
  9. {profile:'writer',state:'disabled',message:'尚未启用',canInstall:true,installedVersion:'1.2.0'},
  10. ]})
  11. beforeEach(()=>{vi.mocked(getHermesBridgeStatus).mockResolvedValue(snapshot())})
  12. afterEach(()=>{vi.clearAllMocks()})
  13. it('shows each Profile and installs only the selected one, with visible progress and restart instructions',async()=>{
  14. let finish!:(value:any)=>void
  15. vi.mocked(installHermesBridge).mockReturnValue(new Promise(done=>{finish=done}))
  16. const wrapper=mount(HermesBridgePanel)
  17. await flushPromises()
  18. expect(wrapper.text()).toContain('default');expect(wrapper.text()).toContain('writer')
  19. await wrapper.get('button[aria-label="安装并启用:writer"]').trigger('click')
  20. expect(installHermesBridge).toHaveBeenCalledWith('writer',true)
  21. expect(wrapper.text()).toContain('安装中…')
  22. expect(wrapper.findAll('button').every(button=>button.attributes('disabled')!==undefined)).toBe(true)
  23. const state=snapshot();state.profiles[1]!.state='restart-required'
  24. finish({profile:'writer',backup:'/backups/writer',message:'安装完成,请重启 Hermes',status:state})
  25. await flushPromises()
  26. expect(wrapper.text()).toContain('待重启');expect(wrapper.get('[role="status"]').text()).toContain('重启 Hermes')
  27. expect(wrapper.text()).toContain('/backups/writer')
  28. await wrapper.findAll('button').find(b=>b.text()==='重新检查')!.trigger('click');await flushPromises()
  29. expect(getHermesBridgeStatus).toHaveBeenCalledTimes(2)
  30. wrapper.unmount()
  31. })
  32. it('provides retry feedback and keeps remote installation unavailable',async()=>{
  33. vi.mocked(getHermesBridgeStatus).mockRejectedValueOnce(new Error('连接失败'))
  34. const wrapper=mount(HermesBridgePanel);await flushPromises()
  35. expect(wrapper.get('[role="alert"]').text()).toBe('连接失败')
  36. const state=snapshot();state.local=false;state.profiles=state.profiles.map(p=>({...p,canInstall:false}));state.message='请在 Hermes 所在节点安装'
  37. vi.mocked(getHermesBridgeStatus).mockResolvedValue(state)
  38. await wrapper.get('button').trigger('click');await flushPromises()
  39. expect(wrapper.text()).toContain('所在节点安装');expect(wrapper.findAll('.install')).toHaveLength(0)
  40. wrapper.unmount()
  41. })
  42. it('shows installation failures without claiming that Hermes has loaded the plugin',async()=>{
  43. vi.mocked(installHermesBridge).mockRejectedValue(new Error('没有写入权限'))
  44. const wrapper=mount(HermesBridgePanel);await flushPromises()
  45. await wrapper.get('button[aria-label="更新工具桥:default"]').trigger('click');await flushPromises()
  46. expect(wrapper.get('[role="alert"]').text()).toBe('没有写入权限')
  47. expect(wrapper.text()).not.toContain('已就绪');expect(wrapper.text()).not.toContain('安装中…')
  48. wrapper.unmount()
  49. })
  50. const managedSnapshot=():HermesBridgeStatus=>({...snapshot(),dashboard:{managed:true,canRestart:true,restarting:false,message:'重启会短暂断开此 Dashboard 下所有 Profile 的连接,完成后自动检查工具桥。'}})
  51. it('shows one instance-wide restart action with progress and the verified result',async()=>{
  52. vi.mocked(getHermesBridgeStatus).mockResolvedValue(managedSnapshot())
  53. let finish!:(value:any)=>void
  54. vi.mocked(restartHermesDashboard).mockReturnValue(new Promise(done=>{finish=done}))
  55. const wrapper=mount(HermesBridgePanel);await flushPromises()
  56. expect(wrapper.findAll('.restart')).toHaveLength(1)
  57. expect(wrapper.text()).toContain('所有 Profile')
  58. await wrapper.get('.restart').trigger('click');await wrapper.get('.restart').trigger('click')
  59. expect(restartHermesDashboard).toHaveBeenCalledTimes(1)
  60. expect(wrapper.get('.restart').text()).toBe('重启中…')
  61. expect(wrapper.attributes('aria-busy')).toBe('true')
  62. expect(wrapper.findAll('button').every(b=>b.attributes('disabled')!==undefined)).toBe(true)
  63. const state=managedSnapshot();state.profiles.forEach(p=>{p.state='ready'})
  64. finish({message:'Hermes Dashboard 已重启,工具桥已就绪。',status:state});await flushPromises()
  65. expect(wrapper.get('[role="status"]').text()).toContain('工具桥已就绪')
  66. expect(wrapper.findAll('.badge.ready')).toHaveLength(2)
  67. expect(wrapper.get('.restart').attributes('disabled')).toBeUndefined()
  68. wrapper.unmount()
  69. })
  70. it('hides restart for unmanaged instances and disables it while tasks or installs are active',async()=>{
  71. const state=managedSnapshot();state.dashboard!.canRestart=false;state.dashboard!.message='当前仍有任务运行,请结束任务后重启。'
  72. vi.mocked(getHermesBridgeStatus).mockResolvedValue(state)
  73. const wrapper=mount(HermesBridgePanel);await flushPromises()
  74. expect(wrapper.get('.restart').attributes('disabled')).toBeDefined()
  75. expect(wrapper.text()).toContain('当前仍有任务运行')
  76. await wrapper.get('.restart').trigger('click');expect(restartHermesDashboard).not.toHaveBeenCalled()
  77. state.dashboard!.managed=false
  78. await wrapper.findAll('button').find(b=>b.text()==='重新检查')!.trigger('click');await flushPromises()
  79. expect(wrapper.find('.restart').exists()).toBe(false)
  80. expect(wrapper.text()).toContain('Hermes 所在节点')
  81. wrapper.unmount()
  82. })
  83. it('refreshes capabilities after failure and preserves the restart error',async()=>{
  84. vi.mocked(getHermesBridgeStatus).mockResolvedValue(managedSnapshot())
  85. vi.mocked(restartHermesDashboard).mockRejectedValue(new Error('Hermes Dashboard 重启失败'))
  86. const wrapper=mount(HermesBridgePanel);await flushPromises()
  87. const state=managedSnapshot();state.dashboard!.canRestart=false;state.dashboard!.managed=false
  88. vi.mocked(getHermesBridgeStatus).mockResolvedValue(state)
  89. await wrapper.get('.restart').trigger('click');await flushPromises()
  90. expect(wrapper.get('[role="alert"]').text()).toContain('重启失败')
  91. expect(wrapper.find('.restart').exists()).toBe(false)
  92. expect(wrapper.text()).not.toContain('工具桥已就绪')
  93. wrapper.unmount()
  94. })