d98e3ab7a1
8 test files, 60 new tests (14 backend + 46 frontend). All green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
140 lines
4.5 KiB
JavaScript
140 lines
4.5 KiB
JavaScript
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)
|
|
})
|
|
})
|