accountSecurityPanel.test.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import { flushPromises, mount } from '@vue/test-utils'
  2. import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
  3. import AccountSecurityDialog from '@/components/app/AccountSecurityDialog.vue'
  4. import AccountSecurityPanel from '@/components/app/AccountSecurityPanel.vue'
  5. const auth = vi.hoisted(() => ({
  6. user: { id: 'admin-1', username: 'owner', role: 'admin' } as { id: string; username: string; role: string } | undefined,
  7. changeCredentials: vi.fn(async () => undefined),
  8. logout: vi.fn(async () => undefined),
  9. }))
  10. vi.mock('@/stores/auth', () => ({ useAuthStore: () => auth }))
  11. async function fill(wrapper: ReturnType<typeof mount>, name: string, value: string) {
  12. await wrapper.get<HTMLInputElement>(`input[name="${name}"]`).setValue(value)
  13. }
  14. beforeEach(() => {
  15. auth.user = { id: 'admin-1', username: 'owner', role: 'admin' }
  16. auth.changeCredentials.mockResolvedValue(undefined)
  17. })
  18. afterEach(() => {
  19. document.body.innerHTML = ''
  20. vi.clearAllMocks()
  21. })
  22. describe('Account security panel', () => {
  23. it('saves the trimmed administrator username and resets its dirty state', async () => {
  24. const wrapper = mount(AccountSecurityPanel, { props: { active: true } })
  25. expect(wrapper.text()).toContain('不会更改 9119 服务账号')
  26. expect(wrapper.emitted('dirty-change')?.at(-1)).toEqual([false])
  27. await fill(wrapper, 'username', ' owner-two ')
  28. await fill(wrapper, 'current-password', 'current-secret')
  29. await fill(wrapper, 'new-password', 'new-secret-123')
  30. await fill(wrapper, 'password-confirmation', 'new-secret-123')
  31. expect(wrapper.text()).toContain('两次输入一致')
  32. expect(wrapper.emitted('dirty-change')?.some(event => event[0] === true)).toBe(true)
  33. await wrapper.get('form').trigger('submit')
  34. await flushPromises()
  35. expect(auth.changeCredentials).toHaveBeenCalledWith({
  36. currentPassword: 'current-secret',
  37. newPassword: 'new-secret-123',
  38. username: 'owner-two',
  39. })
  40. expect(wrapper.emitted('saved')).toHaveLength(1)
  41. expect(wrapper.emitted('dirty-change')?.at(-1)).toEqual([false])
  42. expect(wrapper.get<HTMLInputElement>('input[name="current-password"]').element.value).toBe('')
  43. expect(wrapper.text()).toContain('账号与密码已更新')
  44. })
  45. it('does not expose or submit an administrator username for a regular user', async () => {
  46. auth.user = { id: 'user-1', username: 'member', role: 'user' }
  47. const wrapper = mount(AccountSecurityPanel, { props: { active: true } })
  48. expect(wrapper.find('input[name="username"]').exists()).toBe(false)
  49. await fill(wrapper, 'current-password', 'current-secret')
  50. await fill(wrapper, 'new-password', 'member-secret-123')
  51. await fill(wrapper, 'password-confirmation', 'member-secret-123')
  52. await wrapper.get('form').trigger('submit')
  53. await flushPromises()
  54. expect(auth.changeCredentials).toHaveBeenCalledWith({
  55. currentPassword: 'current-secret',
  56. newPassword: 'member-secret-123',
  57. })
  58. })
  59. it('resets when activated and exposes logout as an independent action', async () => {
  60. const wrapper = mount(AccountSecurityPanel, { props: { active: false } })
  61. await fill(wrapper, 'current-password', 'discard-me')
  62. expect(wrapper.emitted('dirty-change')?.at(-1)).toEqual([true])
  63. await wrapper.setProps({ active: true })
  64. expect(wrapper.get<HTMLInputElement>('input[name="current-password"]').element.value).toBe('')
  65. expect(wrapper.emitted('dirty-change')?.at(-1)).toEqual([false])
  66. await wrapper.get<HTMLButtonElement>('.logout-button').trigger('click')
  67. expect(wrapper.emitted('logout')).toHaveLength(1)
  68. expect(auth.logout).not.toHaveBeenCalled()
  69. })
  70. it('keeps the legacy dialog wrapper operational', async () => {
  71. const wrapper = mount(AccountSecurityDialog, {
  72. attachTo: document.body,
  73. props: { open: true },
  74. })
  75. expect(document.querySelector('[role="dialog"][aria-label="账号安全"]')).not.toBeNull()
  76. document.querySelector<HTMLButtonElement>('.logout-button')!.click()
  77. await flushPromises()
  78. expect(auth.logout).toHaveBeenCalledOnce()
  79. expect(wrapper.emitted('close')).toHaveLength(1)
  80. wrapper.unmount()
  81. })
  82. })