Newer
Older
navi-1 / webclient / tests / unit / components / settings / mcpKeysPanel.test.js
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)
  })
})