computerTargetSelection.test.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import {afterEach,beforeEach,expect,it,vi} from 'vitest'
  2. import {flushPromises,mount} from '@vue/test-utils'
  3. import Preview from '../../src/client/components/workspace/LocalVmChatPanel.vue'
  4. import ComputerPanel from '../../src/client/components/workspace/ComputerPanel.vue'
  5. import Viewer from '../../src/client/views/ComputerViewerView.vue'
  6. import {apiRequest} from '../../src/client/api/client'
  7. vi.mock('../../src/client/api/client',()=>({apiRequest:vi.fn()}))
  8. const route={params:{agentId:'bot'},query:{} as Record<string,unknown>}
  9. vi.mock('vue-router',()=>({useRoute:()=>route}))
  10. const agent={id:'bot',name:'竹儿',profile:'default',nodeId:'local'} as any
  11. const permission={supported:true,authorized:true,screen:true,accessibility:true,ready:true,fullAuthorized:true}
  12. let hosts:any[],readFrame:(key:string)=>unknown
  13. const screenshot=(key:string)=>({id:key,data:key,width:100,height:80,generation:1})
  14. const stubs={AppIcon:true,GrokAuthPanel:true,BrowserToolbar:true}
  15. beforeEach(()=>{
  16. vi.spyOn(document,'hidden','get').mockReturnValue(false)
  17. route.query={}
  18. hosts=[{id:'local',name:'服务器',online:true,local:permission},{id:'client-mac',name:'Mac Studio',online:true,local:permission}]
  19. readFrame=screenshot
  20. vi.mocked(apiRequest).mockImplementation(async(path,options:any)=>{
  21. const url=new URL(path,'http://fixture'),key=url.searchParams.get('host')||url.searchParams.get('backend')||'unspecified'
  22. if(url.pathname==='/api/app/agents')return {agents:[agent]} as any
  23. if(url.pathname.endsWith('/host-tools'))return {serverComputer:true,scriptMachine:true,cloud:true,vm:true} as any
  24. if(url.pathname.endsWith('/desktop-environment'))return {hosts:hosts.map(h=>({...h}))} as any
  25. if(url.pathname.endsWith('/cloud-computer'))return {configured:true,running:true,connected:true} as any
  26. if(url.pathname.endsWith('/cloud-computer/open'))return {} as any
  27. if(url.pathname.endsWith('/local-vm'))return {enabled:true,image:true,container:'running',ready:true} as any
  28. if(url.pathname.endsWith('/frame'))return await readFrame(key) as any
  29. return {mode:options?.method==='POST'?'human':'idle',backend:key==='cloud'?'grok':key==='vm'?'docker':'local',generation:1,hostName:key,controlId:'control',token:'fixture-token'} as any
  30. })
  31. })
  32. afterEach(()=>{vi.restoreAllMocks();vi.clearAllMocks()})
  33. it('refreshes the selected preview immediately and never replaces it with a late frame from another computer',async()=>{
  34. const wrapper=mount(Preview,{props:{agents:[agent],isAdmin:true,active:true},global:{stubs}})
  35. try{
  36. await flushPromises()
  37. expect(wrapper.get('.preview img').attributes('src')).toContain('base64,local')
  38. let resolveClient!:(value:unknown)=>void
  39. readFrame=key=>key==='client-mac'?new Promise(resolve=>{resolveClient=resolve}):screenshot(key)
  40. await wrapper.get('[aria-label="显示的桌面"]').setValue('desktop:client-mac')
  41. await flushPromises()
  42. expect(wrapper.find('.preview img').exists()).toBe(false)
  43. expect(wrapper.get('.preview').attributes('disabled')).toBeDefined()
  44. await wrapper.get('[aria-label="显示的桌面"]').setValue('cloud');await flushPromises()
  45. expect(wrapper.get('.preview img').attributes('src')).toContain('base64,cloud')
  46. resolveClient(screenshot('stale-client'));await flushPromises()
  47. expect(wrapper.get('.preview img').attributes('src')).toContain('base64,cloud')
  48. await wrapper.get('.preview').trigger('click');await flushPromises()
  49. expect(wrapper.emitted('desktop')?.at(-1)).toEqual([agent,'cloud',undefined])
  50. readFrame=screenshot
  51. for(const [key,backend,host] of [['desktop:local','desktop','local'],['desktop:client-mac','desktop','client-mac'],['vm','vm',undefined]]){
  52. await wrapper.get('[aria-label="显示的桌面"]').setValue(key);await flushPromises()
  53. expect(wrapper.get('.preview img').attributes('src')).toContain('base64,'+(host||backend))
  54. await wrapper.get('.preview').trigger('click');await flushPromises()
  55. expect(wrapper.emitted('desktop')?.at(-1)).toEqual([agent,backend,host])
  56. }
  57. }finally{wrapper.unmount()}
  58. })
  59. it('keeps an offline or removed explicit selection instead of opening the server',async()=>{
  60. const wrapper=mount(Preview,{props:{agents:[agent],isAdmin:true,active:true},global:{stubs}})
  61. try{
  62. await flushPromises();await wrapper.get('[aria-label="显示的桌面"]').setValue('desktop:client-mac');await flushPromises()
  63. hosts[1].online=false
  64. await wrapper.setProps({active:false});await wrapper.setProps({active:true});await flushPromises()
  65. expect(wrapper.text()).toContain('这台电脑已离线')
  66. expect(wrapper.find('.preview img').exists()).toBe(false)
  67. hosts=hosts.slice(0,1)
  68. await wrapper.setProps({active:false});await wrapper.setProps({active:true});await flushPromises()
  69. expect(wrapper.text()).toContain('所选电脑已不可用')
  70. expect(wrapper.get('.preview').attributes('disabled')).toBeDefined()
  71. expect(wrapper.emitted('desktop')).toBeUndefined()
  72. }finally{wrapper.unmount()}
  73. })
  74. it.each([
  75. ['desktop','local'],['desktop','client-mac'],['cloud',undefined],['vm',undefined],
  76. ] as const)('opens and controls exactly the requested %s/%s target',async(backend,host)=>{
  77. const wrapper=mount(ComputerPanel,{props:{agents:[agent],backend,host,standalone:true,autoTake:true},global:{stubs}})
  78. try{
  79. await flushPromises()
  80. const call=vi.mocked(apiRequest).mock.calls.find(([path])=>path.includes('/computer/take'))!
  81. const url=new URL(call[0],'http://fixture')
  82. expect(url.searchParams.get('backend')).toBe(backend)
  83. expect(url.searchParams.get('host')).toBe(host??null)
  84. expect(wrapper.get('.computer-screen img').attributes('src')).toContain('base64,'+(host||backend))
  85. }finally{wrapper.unmount()}
  86. })
  87. it('does not auto-take a different computer when the requested host is missing',async()=>{
  88. const wrapper=mount(ComputerPanel,{props:{agents:[agent],backend:'desktop',host:'missing-host',standalone:true,autoTake:true},global:{stubs}})
  89. try{
  90. await flushPromises()
  91. expect(wrapper.get('[role="alert"]').text()).toContain('所选电脑不可用')
  92. expect(vi.mocked(apiRequest).mock.calls.some(([path])=>path.includes('/computer/take'))).toBe(false)
  93. expect(wrapper.find('.computer-screen img').exists()).toBe(false)
  94. }finally{wrapper.unmount()}
  95. })
  96. it('passes the native window URL target into the viewer and rejects malformed targets',async()=>{
  97. route.query={backend:'desktop',host:'client-mac'}
  98. const wrapper=mount(Viewer,{global:{stubs:{ComputerPanel:true}}})
  99. try{
  100. await flushPromises()
  101. expect(wrapper.getComponent(ComputerPanel).props()).toMatchObject({backend:'desktop',host:'client-mac',autoTake:true})
  102. }finally{wrapper.unmount()}
  103. route.query={backend:'vm',host:'client-mac'}
  104. const invalid=mount(Viewer,{global:{stubs:{ComputerPanel:true}}})
  105. try{
  106. await flushPromises();expect(invalid.text()).toContain('所选电脑主机无效')
  107. expect(invalid.findComponent(ComputerPanel).exists()).toBe(false)
  108. }finally{invalid.unmount()}
  109. })