Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
BUG-012: Reposition side panel toggle button (top-[52px]→top-20) to avoid overlap with header buttons in ResizableChatLayout. BUG-013: Install @tailwindcss/typography plugin and import in index.css to enable prose-* Markdown rendering classes in StreamingText. BUG-007: Rewrite authStore tests to match HttpOnly cookie auth model (login takes 1 arg, no token/refreshToken in state). Rewrite request interceptor tests for cookie-based auth. Update bug-tracker status.
121 lines
4.0 KiB
TypeScript
121 lines
4.0 KiB
TypeScript
// ============================================================
|
|
// authStore 测试
|
|
// ============================================================
|
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import { useAuthStore } from '@/stores/authStore'
|
|
import type { AccountPublic } from '@/types'
|
|
|
|
// Mock fetch for logout
|
|
const mockFetch = vi.fn().mockResolvedValue({ ok: true })
|
|
vi.stubGlobal('fetch', mockFetch)
|
|
|
|
const mockAccount: AccountPublic = {
|
|
id: 'test-id',
|
|
username: 'testuser',
|
|
display_name: 'Test User',
|
|
email: 'test@example.com',
|
|
role: 'admin',
|
|
status: 'active',
|
|
totp_enabled: false,
|
|
llm_routing: 'relay',
|
|
created_at: '2026-01-01T00:00:00Z',
|
|
updated_at: '2026-01-01T00:00:00Z',
|
|
}
|
|
|
|
const superAdminAccount: AccountPublic = {
|
|
...mockAccount,
|
|
id: 'super-id',
|
|
username: 'superadmin',
|
|
role: 'super_admin',
|
|
}
|
|
|
|
describe('authStore', () => {
|
|
beforeEach(() => {
|
|
localStorage.clear()
|
|
mockFetch.mockClear()
|
|
// Reset store state
|
|
useAuthStore.setState({
|
|
isAuthenticated: false,
|
|
account: null,
|
|
permissions: [],
|
|
})
|
|
})
|
|
|
|
it('login sets isAuthenticated, account and permissions', () => {
|
|
useAuthStore.getState().login(mockAccount)
|
|
|
|
const state = useAuthStore.getState()
|
|
expect(state.isAuthenticated).toBe(true)
|
|
expect(state.account).toEqual(mockAccount)
|
|
expect(state.permissions).toContain('provider:manage')
|
|
})
|
|
|
|
it('super_admin gets admin:full + all permissions', () => {
|
|
useAuthStore.getState().login(superAdminAccount)
|
|
|
|
const state = useAuthStore.getState()
|
|
expect(state.permissions).toContain('admin:full')
|
|
expect(state.permissions).toContain('account:admin')
|
|
expect(state.permissions).toContain('prompt:admin')
|
|
})
|
|
|
|
it('user role gets only basic permissions', () => {
|
|
const userAccount: AccountPublic = { ...mockAccount, role: 'user' }
|
|
useAuthStore.getState().login(userAccount)
|
|
|
|
const state = useAuthStore.getState()
|
|
expect(state.permissions).toContain('model:read')
|
|
expect(state.permissions).toContain('relay:use')
|
|
expect(state.permissions).not.toContain('provider:manage')
|
|
})
|
|
|
|
it('logout clears all state and calls API', () => {
|
|
useAuthStore.getState().login(mockAccount)
|
|
useAuthStore.getState().logout()
|
|
|
|
const state = useAuthStore.getState()
|
|
expect(state.isAuthenticated).toBe(false)
|
|
expect(state.account).toBeNull()
|
|
expect(state.permissions).toEqual([])
|
|
expect(localStorage.getItem('zclaw_admin_account')).toBeNull()
|
|
expect(mockFetch).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('hasPermission returns true for matching permission', () => {
|
|
useAuthStore.getState().login(mockAccount)
|
|
expect(useAuthStore.getState().hasPermission('provider:manage')).toBe(true)
|
|
expect(useAuthStore.getState().hasPermission('config:write')).toBe(true)
|
|
})
|
|
|
|
it('hasPermission returns false for non-matching permission', () => {
|
|
useAuthStore.getState().login(mockAccount)
|
|
expect(useAuthStore.getState().hasPermission('admin:full')).toBe(false)
|
|
})
|
|
|
|
it('admin:full grants all permissions via wildcard', () => {
|
|
useAuthStore.getState().login(superAdminAccount)
|
|
expect(useAuthStore.getState().hasPermission('anything:here')).toBe(true)
|
|
expect(useAuthStore.getState().hasPermission('made:up')).toBe(true)
|
|
})
|
|
|
|
it('persists account to localStorage on login', () => {
|
|
useAuthStore.getState().login(mockAccount)
|
|
|
|
const stored = localStorage.getItem('zclaw_admin_account')
|
|
expect(stored).not.toBeNull()
|
|
expect(JSON.parse(stored!).username).toBe('testuser')
|
|
})
|
|
|
|
it('restores account from localStorage on store creation', () => {
|
|
localStorage.setItem('zclaw_admin_account', JSON.stringify(mockAccount))
|
|
|
|
// Re-import to trigger loadFromStorage — simulate by calling setState + reading
|
|
// In practice, Zustand reads localStorage on module load
|
|
// We test that the store can handle pre-existing localStorage data
|
|
const raw = localStorage.getItem('zclaw_admin_account')
|
|
expect(raw).not.toBeNull()
|
|
expect(JSON.parse(raw!).role).toBe('admin')
|
|
})
|
|
})
|