feat(07-05): frontend AI config client helpers + AdminAiConfigTab system section + Vitest tests
- Add getAiConfig/saveAiConfig/testAiConnection to frontend/src/api/client.js - AdminAiConfigTab.vue: add System AI Providers section above existing per-user table - Per-provider accordion (10 providers from PROVIDER_DEFAULTS) - Write-only API key field (never pre-filled), base URL, model, context_chars inputs - Set Active (atomic flip), Save, Test Connection buttons with inline badges - Existing per-user ai-config table and saveConfig logic untouched (Pitfall 6) - Create frontend/tests/api.spec.js: 4 Vitest tests for getAiConfig/saveAiConfig/testAiConnection - All 127 frontend tests pass, build exits 0 (D-11 frontend coverage)
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Vitest unit tests for the system AI provider client helpers.
|
||||
*
|
||||
* Tests verify that getAiConfig, saveAiConfig, and testAiConnection
|
||||
* construct the correct fetch payloads (path, method, body, encoding).
|
||||
*
|
||||
* D-11 frontend coverage — CLAUDE.md mandate: every new function has at least one test.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
|
||||
// ── Mock Pinia auth store (request() lazy-imports it) ────────────────────────
|
||||
// The request() helper in client.js does:
|
||||
// const { useAuthStore } = await import('../stores/auth.js')
|
||||
// const authStore = useAuthStore()
|
||||
// We stub the module so it returns a store with no accessToken,
|
||||
// preventing Pinia initialization in unit tests.
|
||||
vi.mock('../src/stores/auth.js', () => ({
|
||||
useAuthStore: () => ({ accessToken: null }),
|
||||
}))
|
||||
|
||||
// ── Mock global fetch ─────────────────────────────────────────────────────────
|
||||
let fetchMock
|
||||
|
||||
beforeEach(() => {
|
||||
fetchMock = vi.fn(() =>
|
||||
Promise.resolve(
|
||||
new Response(JSON.stringify({ ok: true }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
)
|
||||
)
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
})
|
||||
|
||||
// ── Import helpers under test ─────────────────────────────────────────────────
|
||||
import { getAiConfig, saveAiConfig, testAiConnection } from '../src/api/client.js'
|
||||
|
||||
// ── Tests ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('getAiConfig', () => {
|
||||
it('issues GET /api/admin/ai-config', async () => {
|
||||
await getAiConfig()
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledOnce()
|
||||
const [url, init] = fetchMock.mock.calls[0]
|
||||
expect(url).toMatch(/\/api\/admin\/ai-config$/)
|
||||
expect(init.method).toBe('GET')
|
||||
})
|
||||
})
|
||||
|
||||
describe('saveAiConfig', () => {
|
||||
it('issues PUT with JSON body containing provider_id and api_key', async () => {
|
||||
const payload = { provider_id: 'openai', api_key: 'sk-x' }
|
||||
await saveAiConfig(payload)
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledOnce()
|
||||
const [url, init] = fetchMock.mock.calls[0]
|
||||
expect(url).toMatch(/\/api\/admin\/ai-config$/)
|
||||
expect(init.method).toBe('PUT')
|
||||
expect(init.headers?.['Content-Type'] || init.headers?.get?.('Content-Type')).toBe('application/json')
|
||||
expect(JSON.parse(init.body)).toEqual({ provider_id: 'openai', api_key: 'sk-x' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('testAiConnection', () => {
|
||||
it('issues GET with URL-encoded provider_id', async () => {
|
||||
// Provider ID with a space to verify encodeURIComponent is applied
|
||||
await testAiConnection('open ai')
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledOnce()
|
||||
const [url, init] = fetchMock.mock.calls[0]
|
||||
expect(url).toContain('/api/admin/ai-config/test-connection?provider_id=open%20ai')
|
||||
expect(init.method).toBe('GET')
|
||||
})
|
||||
|
||||
it('issues GET with a plain provider_id that needs no encoding', async () => {
|
||||
await testAiConnection('openai')
|
||||
|
||||
const [url] = fetchMock.mock.calls[0]
|
||||
expect(url).toContain('/api/admin/ai-config/test-connection?provider_id=openai')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user