import { describe, it, expect, vi, beforeEach } from 'vitest' import { mount, flushPromises } from '@vue/test-utils' // Mock api/client.js vi.mock('../../../api/client.js', () => ({ adminListUsers: vi.fn(), adminUpdateAiConfig: vi.fn(), })) import AdminAiConfigTab from '../AdminAiConfigTab.vue' import * as api from '../../../api/client.js' function makeUser(overrides = {}) { return { id: overrides.id ?? 'user-1', email: overrides.email ?? 'alice@example.com', handle: overrides.handle ?? 'alice', role: 'user', is_active: true, ai_provider: overrides.ai_provider ?? null, ai_model: overrides.ai_model ?? null, ...overrides, } } beforeEach(() => { vi.clearAllMocks() }) // ── onMounted: calls adminListUsers() ───────────────────────────────────────── describe('AdminAiConfigTab — onMounted', () => { it('calls adminListUsers() on mount', async () => { api.adminListUsers.mockResolvedValue({ items: [] }) mount(AdminAiConfigTab) await flushPromises() expect(api.adminListUsers).toHaveBeenCalledTimes(1) }) it('shows "No users yet" empty state when no users', async () => { api.adminListUsers.mockResolvedValue({ items: [] }) const w = mount(AdminAiConfigTab) await flushPromises() expect(w.text()).toContain('No users yet') }) it('renders a row per user', async () => { api.adminListUsers.mockResolvedValue({ items: [ makeUser({ id: 'u1', email: 'alice@example.com' }), makeUser({ id: 'u2', email: 'bob@example.com' }), ], }) const w = mount(AdminAiConfigTab) await flushPromises() expect(w.text()).toContain('alice@example.com') expect(w.text()).toContain('bob@example.com') }) it('pre-populates existing ai_provider and ai_model in inputs', async () => { api.adminListUsers.mockResolvedValue({ items: [makeUser({ id: 'u1', ai_provider: 'openai', ai_model: 'gpt-4o' })], }) const w = mount(AdminAiConfigTab) await flushPromises() // The select should have openai selected const select = w.find('select') expect(select.element.value).toBe('openai') // The model input should have gpt-4o const modelInput = w.find('input[type="text"]') expect(modelInput.element.value).toBe('gpt-4o') }) }) // ── saveConfig: calls adminUpdateAiConfig(id, provider, model) ──────────────── describe('AdminAiConfigTab — saveConfig', () => { it('calls adminUpdateAiConfig with user id, provider, and model on Save click', async () => { api.adminListUsers.mockResolvedValue({ items: [makeUser({ id: 'u1', email: 'alice@example.com', ai_provider: '', ai_model: '' })], }) api.adminUpdateAiConfig.mockResolvedValue({}) const w = mount(AdminAiConfigTab) await flushPromises() // Select a provider const select = w.find('select') await select.setValue('anthropic') // Enter a model const modelInput = w.find('input[type="text"]') await modelInput.setValue('claude-3-5-sonnet') // Click Save const saveBtn = w.findAll('button').find(b => b.text().includes('Save')) expect(saveBtn).toBeTruthy() await saveBtn.trigger('click') await flushPromises() expect(api.adminUpdateAiConfig).toHaveBeenCalledWith('u1', 'anthropic', 'claude-3-5-sonnet') }) it('shows "Saved" confirmation text after successful save', async () => { api.adminListUsers.mockResolvedValue({ items: [makeUser({ id: 'u1', email: 'alice@example.com' })], }) api.adminUpdateAiConfig.mockResolvedValue({}) const w = mount(AdminAiConfigTab) await flushPromises() const saveBtn = w.findAll('button').find(b => b.text().includes('Save')) await saveBtn.trigger('click') await flushPromises() expect(w.text()).toContain('Saved') }) it('passes null for empty provider string to API', async () => { api.adminListUsers.mockResolvedValue({ items: [makeUser({ id: 'u1', ai_provider: null, ai_model: null })], }) api.adminUpdateAiConfig.mockResolvedValue({}) const w = mount(AdminAiConfigTab) await flushPromises() // Select is empty string '' — saveConfig converts '' to null const saveBtn = w.findAll('button').find(b => b.text().includes('Save')) await saveBtn.trigger('click') await flushPromises() // Called with null for both empty provider and model expect(api.adminUpdateAiConfig).toHaveBeenCalledWith('u1', null, null) }) })