Files
kite/frontend/src/components/settings/__tests__/SettingsAccountTab.test.js
T
curo1305andClaude Sonnet 4.6 8f8bfa5539 test(phase-07.1): add Nyquist validation tests for sessions_revoked frontend behavior
3 positive + 2 negative Vitest tests covering CR-01/02/03 toast UX:
- SettingsAccountTab: toast appears/hidden after changePassword (CR-01)
- SettingsAccountTab: toast appears after disableTotp (CR-03)
- TotpEnrollment: inline alert appears/hidden after enable_totp (CR-02)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 12:54:14 +02:00

209 lines
7.0 KiB
JavaScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { createRouter, createMemoryHistory } from 'vue-router'
// Mock auth store before any imports
vi.mock('../../../stores/auth.js', () => ({
useAuthStore: vi.fn(),
}))
// Mock api/client to avoid HTTP calls
vi.mock('../../../api/client.js', () => ({
changePassword: vi.fn(),
totpDisable: vi.fn(),
}))
import { useAuthStore } from '../../../stores/auth.js'
import { changePassword as changePasswordMock, totpDisable as totpDisableMock } from '../../../api/client.js'
import SettingsAccountTab from '../SettingsAccountTab.vue'
// Minimal router — required because the component calls useRouter() in script setup
const testRouter = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: { template: '<div />' } },
{ path: '/login', component: { template: '<div />' } },
{ path: '/settings', component: { template: '<div />' } },
],
})
const globalStubs = {
TotpEnrollment: { template: '<div data-stub="TotpEnrollment" />' },
ConfirmBlock: { template: '<div data-stub="ConfirmBlock" />', props: ['message', 'confirmLabel', 'cancelLabel'] },
PasswordStrengthBar: { template: '<div data-stub="PasswordStrengthBar" />', props: ['password'] },
AppSpinner: { template: '<span />' },
}
beforeEach(() => {
setActivePinia(createPinia())
vi.clearAllMocks()
})
// ─── GAP 1 & 2: Sessions revoked toast (CR-01, CR-03) ──────────────────────
describe('SettingsAccountTab — sessions revoked toast (CR-01, CR-03)', () => {
// Stub for ConfirmBlock that renders a button triggering @confirmed emission
const ConfirmBlockEmitter = {
name: 'ConfirmBlock',
template: '<div data-stub="ConfirmBlock"><button data-action="confirm" @click="$emit(\'confirmed\')">confirm</button></div>',
props: ['message', 'confirmLabel', 'cancelLabel'],
emits: ['confirmed', 'cancelled'],
}
it('shows "Other sessions have been terminated." toast after changePassword returns sessions_revoked > 0 (CR-01)', async () => {
useAuthStore.mockReturnValue({
user: { email: 'test@example.com', handle: 'testuser', role: 'user', totp_enabled: false },
logoutAll: vi.fn(),
})
vi.mocked(changePasswordMock).mockResolvedValueOnce({
message: 'Password updated',
sessions_revoked: 2,
})
const wrapper = mount(SettingsAccountTab, {
global: {
plugins: [createPinia(), testRouter],
stubs: { ...globalStubs, ConfirmBlock: ConfirmBlockEmitter },
},
})
// Submit the change-password form to invoke changePassword()
await wrapper.find('form').trigger('submit')
await flushPromises()
expect(wrapper.text()).toContain('Other sessions have been terminated.')
})
it('does NOT show toast after changePassword when sessions_revoked is 0 (CR-01 negative)', async () => {
useAuthStore.mockReturnValue({
user: { email: 'test@example.com', handle: 'testuser', role: 'user', totp_enabled: false },
logoutAll: vi.fn(),
})
vi.mocked(changePasswordMock).mockResolvedValueOnce({
message: 'Password updated',
sessions_revoked: 0,
})
const wrapper = mount(SettingsAccountTab, {
global: {
plugins: [createPinia(), testRouter],
stubs: { ...globalStubs, ConfirmBlock: ConfirmBlockEmitter },
},
})
await wrapper.find('form').trigger('submit')
await flushPromises()
expect(wrapper.text()).not.toContain('Other sessions have been terminated.')
})
it('shows "Other sessions have been terminated." toast after disableTotp returns sessions_revoked > 0 (CR-03)', async () => {
const user = { email: 'test@example.com', handle: 'testuser', role: 'user', totp_enabled: true }
useAuthStore.mockReturnValue({
user,
logoutAll: vi.fn(),
})
vi.mocked(totpDisableMock).mockResolvedValueOnce({
message: 'TOTP disabled',
sessions_revoked: 1,
})
const wrapper = mount(SettingsAccountTab, {
global: {
plugins: [createPinia(), testRouter],
stubs: { ...globalStubs, ConfirmBlock: ConfirmBlockEmitter },
},
})
// Click "Disable 2FA" to reveal the ConfirmBlock
const disableBtn = wrapper.findAll('button').find(b => b.text().includes('Disable 2FA'))
await disableBtn.trigger('click')
// Click the confirm button rendered by the ConfirmBlockEmitter stub
await wrapper.find('[data-action="confirm"]').trigger('click')
await flushPromises()
expect(wrapper.text()).toContain('Other sessions have been terminated.')
})
})
describe('SettingsAccountTab — four section headings (AUTH-03, AUTH-04)', () => {
it('renders all 4 required section headings when totp_enabled is false', () => {
useAuthStore.mockReturnValue({
user: { email: 'test@example.com', handle: 'testuser', role: 'user', totp_enabled: false },
logoutAll: vi.fn(),
})
const wrapper = mount(SettingsAccountTab, {
global: {
plugins: [createPinia(), testRouter],
stubs: globalStubs,
},
})
const text = wrapper.text()
expect(text).toContain('Account information')
expect(text).toContain('Two-factor authentication')
expect(text).toContain('Change password')
expect(text).toContain('Sessions')
})
it('renders TotpEnrollment stub when totp_enabled is false', () => {
useAuthStore.mockReturnValue({
user: { email: 'test@example.com', handle: 'testuser', role: 'user', totp_enabled: false },
logoutAll: vi.fn(),
})
const wrapper = mount(SettingsAccountTab, {
global: {
plugins: [createPinia(), testRouter],
stubs: globalStubs,
},
})
expect(wrapper.find('[data-stub="TotpEnrollment"]').exists()).toBe(true)
})
it('shows "Enabled" status and "Disable 2FA" button when totp_enabled is true', () => {
useAuthStore.mockReturnValue({
user: { email: 'test@example.com', handle: 'testuser', role: 'user', totp_enabled: true },
logoutAll: vi.fn(),
})
const wrapper = mount(SettingsAccountTab, {
global: {
plugins: [createPinia(), testRouter],
stubs: globalStubs,
},
})
// "Enabled" text must appear (inside the 2FA section)
expect(wrapper.text()).toContain('Enabled')
// "Disable 2FA" button must be present
const buttons = wrapper.findAll('button')
const disableBtn = buttons.find(b => b.text().includes('Disable 2FA'))
expect(disableBtn).toBeDefined()
})
it('does NOT render TotpEnrollment stub when totp_enabled is true', () => {
useAuthStore.mockReturnValue({
user: { email: 'test@example.com', handle: 'testuser', role: 'user', totp_enabled: true },
logoutAll: vi.fn(),
})
const wrapper = mount(SettingsAccountTab, {
global: {
plugins: [createPinia(), testRouter],
stubs: globalStubs,
},
})
expect(wrapper.find('[data-stub="TotpEnrollment"]').exists()).toBe(false)
})
})