hostToolsPanel.test.ts 4.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import { flushPromises, mount } from '@vue/test-utils'
  2. import { beforeEach, expect, it, vi } from 'vitest'
  3. import HostToolsPanel from '@/components/app/HostToolsPanel.vue'
  4. import { apiRequest } from '@/api/client'
  5. vi.mock('@/api/client', () => ({ apiRequest: vi.fn() }))
  6. const request = vi.mocked(apiRequest)
  7. const endpoint = '/api/app/admin/desktop-hosts'
  8. const firstId = '1a111111-1111-4111-8111-111111111111'
  9. const secondId = '2b222222-2222-4222-8222-222222222222'
  10. let hosts: { id: string; name: string; displayName: string; enabled: boolean; online: boolean; createdAt: number }[]
  11. beforeEach(() => {
  12. request.mockReset()
  13. hosts = [
  14. { id: firstId, name: '同名电脑', displayName: '', enabled: true, online: false, createdAt: 1_700_000_000_000 },
  15. { id: secondId, name: '同名电脑', displayName: '', enabled: true, online: true, createdAt: 1_710_000_000_000 },
  16. { id: 'disabled-host', name: '已停用电脑', displayName: '', enabled: false, online: false, createdAt: 1 },
  17. ]
  18. request.mockImplementation(async (path, options) => {
  19. if (path === endpoint) return { hosts: structuredClone(hosts), localName: '' } as never
  20. if (path === '/api/app/settings/host-tools') return { approvalPolicy: 'allow', scriptMachine: true, serverComputer: true, vm: true, cloud: true } as never
  21. const host = hosts.find(item => path === `${endpoint}/${item.id}/name` || path === `${endpoint}/${item.id}`)
  22. if (host && options?.method === 'PUT') host.displayName = (options.body as { name: string }).name
  23. else if (host && options?.method === 'DELETE') host.enabled = false
  24. else throw new Error(`Unexpected request: ${path}`)
  25. return { ok: true } as never
  26. })
  27. })
  28. it('distinguishes same-name registrations by status, ID and pairing time without merging them', async () => {
  29. const wrapper = mount(HostToolsPanel)
  30. await flushPromises()
  31. expect(wrapper.findAll('.computer')).toHaveLength(3)
  32. const first = wrapper.get(`[data-host-id="${firstId}"]`)
  33. const second = wrapper.get(`[data-host-id="${secondId}"]`)
  34. expect(first.text()).toContain('未连接')
  35. expect(second.text()).toContain('已连接')
  36. expect(first.text()).toContain('编号 1a111111')
  37. expect(second.text()).toContain('编号 2b222222')
  38. expect(first.text()).toContain('配对于')
  39. expect(wrapper.text()).toContain('有同名的配对记录')
  40. expect(wrapper.text()).not.toContain('已停用电脑')
  41. expect(wrapper.get('[data-host-id="local"]').find('.remove-host').exists()).toBe(false)
  42. expect(request.mock.calls.some(([, options]) => options?.method === 'DELETE')).toBe(false)
  43. wrapper.unmount()
  44. })
  45. it('only disables the explicitly confirmed registration, even when names match', async () => {
  46. const wrapper = mount(HostToolsPanel)
  47. await flushPromises()
  48. const first = wrapper.get(`[data-host-id="${firstId}"]`)
  49. await first.get('.remove-host').trigger('click')
  50. expect(request.mock.calls.some(([, options]) => options?.method === 'DELETE')).toBe(false)
  51. await first.get('.remove-confirmation .remove-host').trigger('click')
  52. await flushPromises()
  53. expect(request).toHaveBeenCalledWith(`${endpoint}/${firstId}`, { method: 'DELETE' })
  54. expect(wrapper.find(`[data-host-id="${firstId}"]`).exists()).toBe(false)
  55. expect(wrapper.find(`[data-host-id="${secondId}"]`).exists()).toBe(true)
  56. expect(wrapper.text()).toContain('这条配对记录已停用')
  57. wrapper.unmount()
  58. })
  59. it('keeps the record and shows an error when disabling fails', async () => {
  60. const wrapper = mount(HostToolsPanel)
  61. await flushPromises()
  62. request.mockRejectedValueOnce(new Error('服务暂时不可用'))
  63. const first = wrapper.get(`[data-host-id="${firstId}"]`)
  64. await first.get('.remove-host').trigger('click')
  65. await first.get('.remove-confirmation .remove-host').trigger('click')
  66. await flushPromises()
  67. expect(wrapper.find(`[data-host-id="${firstId}"]`).exists()).toBe(true)
  68. expect(wrapper.get('[role="alert"]').text()).toContain('服务暂时不可用')
  69. wrapper.unmount()
  70. })
  71. it('renames by registration ID and preserves another computer’s unsaved name', async () => {
  72. const wrapper = mount(HostToolsPanel)
  73. await flushPromises()
  74. const first = wrapper.get(`[data-host-id="${firstId}"]`)
  75. const second = wrapper.get(`[data-host-id="${secondId}"]`)
  76. await first.get('input').setValue('旧电脑')
  77. await second.get('input').setValue('新电脑')
  78. await first.get('.name-editor button').trigger('click')
  79. await flushPromises()
  80. expect(request).toHaveBeenCalledWith(`${endpoint}/${firstId}/name`, { method: 'PUT', body: { name: '旧电脑' } })
  81. expect(second.get<HTMLInputElement>('input').element.value).toBe('新电脑')
  82. hosts[1]!.online = false
  83. await wrapper.get('.names-heading button').trigger('click')
  84. await flushPromises()
  85. expect(second.text()).toContain('未连接')
  86. expect(second.get<HTMLInputElement>('input').element.value).toBe('新电脑')
  87. wrapper.unmount()
  88. })