94 lines
5.5 KiB
TypeScript
94 lines
5.5 KiB
TypeScript
import { expect, test, type Page } from '@playwright/test'
|
|
import { authenticate, mockHermesApi, TEST_ACCESS_KEY } from './fixtures'
|
|
|
|
async function setup(page: Page, custom = false) {
|
|
await authenticate(page, TEST_ACCESS_KEY, 'research')
|
|
await mockHermesApi(page)
|
|
const group = {
|
|
provider: custom ? 'custom:proxy' : 'minimax-cn',
|
|
label: custom ? 'Proxy' : 'MiniMax (China)',
|
|
base_url: 'https://provider.example.test/v1', api_key: 'existing-test-key',
|
|
models: ['original-model'], available_models: ['original-model'],
|
|
builtin: !custom, credentials_editable: !custom,
|
|
...(!custom ? { base_url_env: 'MINIMAX_CN_BASE_URL' } : { provider_source: 'providers', provider_key: 'proxy_config' }),
|
|
}
|
|
await page.route(url => url.pathname === '/api/hermes/available-models', route => route.fulfill({ json: {
|
|
default: 'original-model', default_provider: group.provider, groups: [group], allProviders: [group],
|
|
profiles: [{ profile: 'research', default: 'original-model', default_provider: group.provider, groups: [group] }],
|
|
} }))
|
|
await page.goto('/#/hermes/models')
|
|
const card = page.locator('.provider-card').filter({ hasText: group.label })
|
|
await expect(card).toBeVisible()
|
|
return { group, card }
|
|
}
|
|
|
|
async function fillUrl(page: Page, url: string) {
|
|
await page.locator('.provider-edit-form .arco-form-item').filter({ hasText: 'Base URL' }).locator('input').fill(url)
|
|
}
|
|
|
|
test('card fetches the live catalog, updates the list, and retains its default model', async ({ page }) => {
|
|
const { group, card } = await setup(page)
|
|
let requestBody: any
|
|
let profileHeader: string | undefined
|
|
await page.route(url => url.pathname === '/api/hermes/provider-models', async route => {
|
|
requestBody = route.request().postDataJSON()
|
|
profileHeader = route.request().headers()['x-hermes-profile']
|
|
group.models = ['original-model', 'latest-model']
|
|
group.available_models = [...group.models]
|
|
await route.fulfill({ json: { models: [...group.models] } })
|
|
})
|
|
await card.getByRole('button', { name: 'Fetch models from upstream' }).click()
|
|
await expect(card.locator('.model-tag', { hasText: 'latest-model' })).toBeVisible()
|
|
await expect(card.locator('.default-model-select')).toContainText('original-model')
|
|
expect(requestBody).toMatchObject({ provider: 'minimax-cn', api_key: 'existing-test-key', update_cache: true })
|
|
expect(profileHeader).toBe('research')
|
|
})
|
|
|
|
test('editing built-in URL and key uses PUT and updates the card without recreation', async ({ page }) => {
|
|
const { group, card } = await setup(page)
|
|
const writes: Array<{ method: string; body: any; profile?: string }> = []
|
|
await page.route(url => url.pathname.startsWith('/api/hermes/config/providers/'), async route => {
|
|
const body = route.request().postDataJSON()
|
|
writes.push({ method: route.request().method(), body, profile: route.request().headers()['x-hermes-profile'] })
|
|
group.base_url = body.base_url
|
|
if (body.api_key) group.api_key = body.api_key
|
|
await route.fulfill({ json: { success: true } })
|
|
})
|
|
await card.getByRole('button', { name: 'Edit', exact: true }).click()
|
|
await fillUrl(page, 'https://new.example.test/v1')
|
|
await page.locator('.provider-edit-form input[type=password]').fill('new-test-key')
|
|
await page.getByRole('button', { name: 'Save', exact: true }).click()
|
|
await expect(page.locator('.provider-edit-form')).toHaveCount(0)
|
|
await expect(card).toContainText('https://new.example.test/v1')
|
|
expect(writes).toEqual([{ method: 'PUT', body: { base_url: 'https://new.example.test/v1', api_key: 'new-test-key' }, profile: 'research' }])
|
|
await expect(card.locator('.default-model-select')).toContainText('original-model')
|
|
})
|
|
|
|
test('editing a dict-backed provider preserves a blank key and sends its storage identity', async ({ page }) => {
|
|
const { card } = await setup(page, true)
|
|
let requestBody: any
|
|
await page.route(url => url.pathname.startsWith('/api/hermes/config/providers/'), async route => {
|
|
requestBody = route.request().postDataJSON()
|
|
await route.fulfill({ json: { success: true } })
|
|
})
|
|
await card.getByRole('button', { name: 'Edit', exact: true }).click()
|
|
await expect(page.locator('.provider-edit-form input[type=password]')).toHaveValue('')
|
|
await fillUrl(page, 'https://changed.example.test/v1')
|
|
await page.getByRole('button', { name: 'Save', exact: true }).click()
|
|
await expect(page.locator('.provider-edit-form')).toHaveCount(0)
|
|
expect(requestBody).toEqual({ base_url: 'https://changed.example.test/v1', source: 'providers', providerKey: 'proxy_config' })
|
|
})
|
|
|
|
test('failed upstream fetch keeps the existing catalog; failed edit stays open', async ({ page }) => {
|
|
const { card } = await setup(page)
|
|
await page.route(url => url.pathname === '/api/hermes/provider-models', route => route.fulfill({ status: 502, json: { error: 'Upstream unavailable' } }))
|
|
await card.getByRole('button', { name: 'Fetch models from upstream' }).click()
|
|
await expect(page.getByText('API Error 502: Upstream unavailable', { exact: true })).toBeVisible()
|
|
await expect(card.locator('.model-tag', { hasText: 'original-model' })).toBeVisible()
|
|
await page.route(url => url.pathname.startsWith('/api/hermes/config/providers/'), route => route.fulfill({ status: 500, json: { error: 'Save failed' } }))
|
|
await card.getByRole('button', { name: 'Edit', exact: true }).click()
|
|
await page.getByRole('button', { name: 'Save', exact: true }).click()
|
|
await expect(page.getByText('API Error 500: Save failed', { exact: true })).toBeVisible()
|
|
await expect(page.locator('.provider-edit-form')).toBeVisible()
|
|
})
|