import { describe, it, expect, vi, beforeEach } from 'vitest'
import { createPinia, setActivePinia } from 'pinia'
import { useMcpKeysStore } from '@/stores/mcpKeys.js'
vi.mock('@/api/index.js', () => ({
getMcpKeys: vi.fn(),
saveMcpKey: vi.fn(),
resetMcpKey: vi.fn(),
}))
import * as api from '@/api/index.js'
const ITEMS = [
{
server_name: 'http-server',
transport: 'streamable_http',
key_type: 'header',
key_location: 'Authorization',
prefix: 'Bearer ',
has_key: false,
updated_at: null,
},
{
server_name: 'stdio-server',
transport: 'stdio',
key_type: 'env',
key_location: 'API_KEY',
prefix: null,
has_key: true,
updated_at: '2026-10-07T12:00:00Z',
},
]
describe('MCP keys store', () => {
beforeEach(() => {
setActivePinia(createPinia())
vi.clearAllMocks()
// Fresh clone per test — the store mutates fetched items in place.
api.getMcpKeys.mockImplementation(async () => ({ items: structuredClone(ITEMS) }))
})
it('fetchServers GETs and unpacks the items array', async () => {
api.getMcpKeys.mockImplementation(async () => ({ items: structuredClone(ITEMS) }))
const store = useMcpKeysStore()
await store.fetchServers()
expect(api.getMcpKeys).toHaveBeenCalledTimes(1)
expect(store.servers).toEqual(ITEMS)
expect(store.loading).toBe(false)
})
it('fetchServers handles an empty list', async () => {
api.getMcpKeys.mockResolvedValue({ items: [] })
const store = useMcpKeysStore()
await store.fetchServers()
expect(store.servers).toEqual([])
})
it('saveKey PUTs and patches the matching server entry', async () => {
api.getMcpKeys.mockImplementation(async () => ({ items: structuredClone(ITEMS) }))
api.saveMcpKey.mockResolvedValue({
server_name: 'http-server',
has_key: true,
updated_at: '2026-10-08T00:00:00Z',
})
const store = useMcpKeysStore()
await store.fetchServers()
await store.saveKey('http-server', 'sk-user')
expect(api.saveMcpKey).toHaveBeenCalledWith('http-server', 'sk-user')
expect(store.servers[0].has_key).toBe(true)
expect(store.servers[0].updated_at).toBe('2026-10-08T00:00:00Z')
expect(store.savingServer).toBe(null)
})
it('saveKey rethrows on failure and clears savingServer', async () => {
api.getMcpKeys.mockImplementation(async () => ({ items: structuredClone(ITEMS) }))
api.saveMcpKey.mockRejectedValue(new Error('500'))
const store = useMcpKeysStore()
await store.fetchServers()
await expect(store.saveKey('http-server', 'sk-user')).rejects.toThrow('500')
expect(store.servers[0].has_key).toBe(false)
expect(store.savingServer).toBe(null)
})
it('resetKey DELETEs and clears the matching entry', async () => {
api.getMcpKeys.mockImplementation(async () => ({ items: structuredClone(ITEMS) }))
api.resetMcpKey.mockResolvedValue(undefined)
const store = useMcpKeysStore()
await store.fetchServers()
await store.resetKey('stdio-server')
expect(api.resetMcpKey).toHaveBeenCalledWith('stdio-server')
expect(store.servers[1].has_key).toBe(false)
expect(store.servers[1].updated_at).toBe(null)
expect(store.savingServer).toBe(null)
})
})