import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { useMcpKeysStore } from '@/stores/mcpKeys.js'
import McpKeysPanel from '@/components/settings/McpKeysPanel.vue'
vi.mock('@/api/index.js', () => ({
getMcpKeys: vi.fn(),
saveMcpKey: vi.fn(),
resetMcpKey: vi.fn(),
}))
vi.mock('@/composables/useConfirm.js', () => ({
useConfirm: () => () => Promise.resolve(true),
}))
// No GnToastProvider in tests — swallow toast calls.
vi.mock('gnexus-ui-kit/vue', async (importOriginal) => {
const mod = await importOriginal()
const toast = {
show: () => {}, info: () => {}, success: () => {},
warning: () => {}, danger: () => {}, error: () => {}, close: () => {},
}
return { ...mod, useToast: () => toast }
})
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('McpKeysPanel', () => {
beforeEach(() => {
setActivePinia(createPinia())
vi.clearAllMocks()
// Fresh clone per test — the store mutates items in place.
api.getMcpKeys.mockImplementation(async () => ({ items: structuredClone(ITEMS) }))
api.saveMcpKey.mockImplementation(async (server, key) => ({
server_name: server, has_key: true, updated_at: '2026-10-08T00:00:00Z',
}))
api.resetMcpKey.mockResolvedValue(undefined)
})
it('renders one row per eligible server with its key location', async () => {
const wrapper = mount(McpKeysPanel)
await new Promise(r => setTimeout(r))
expect(wrapper.text()).toContain('MCP Keys')
expect(wrapper.text()).toContain('http-server')
expect(wrapper.text()).toContain('stdio-server')
expect(wrapper.text()).toContain('Authorization header (Bearer …)')
expect(wrapper.text()).toContain('API_KEY env')
})
it('does not render at all when there are no eligible servers', async () => {
api.getMcpKeys.mockResolvedValue({ items: [] })
const wrapper = mount(McpKeysPanel)
await new Promise(r => setTimeout(r))
expect(wrapper.find('.mcp-keys-panel').exists()).toBe(false)
})
it('saving a key PUTs it and marks the server as keyed', async () => {
const wrapper = mount(McpKeysPanel)
await new Promise(r => setTimeout(r))
const input = wrapper.findAll('input').find(i => i.attributes('type') === 'password')
input.element.value = 'sk-user'
await input.trigger('input')
const saveBtn = wrapper
.findAll('button')
.find(b => b.text().includes('Save'))
await saveBtn.trigger('click')
await new Promise(r => setTimeout(r))
expect(api.saveMcpKey).toHaveBeenCalledWith('http-server', 'sk-user')
const store = useMcpKeysStore()
expect(store.servers[0].has_key).toBe(true)
// input cleared after save
expect(input.element.value).toBe('')
})
it('reset asks for confirmation and DELETEs the saved key', async () => {
const wrapper = mount(McpKeysPanel)
await new Promise(r => setTimeout(r))
const resetBtn = wrapper
.findAll('button')
.find(b => b.text().includes('Reset to default'))
expect(resetBtn).toBeTruthy()
await resetBtn.trigger('click')
await new Promise(r => setTimeout(r))
expect(api.resetMcpKey).toHaveBeenCalledWith('stdio-server')
const store = useMcpKeysStore()
expect(store.servers[1].has_key).toBe(false)
})
})