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() })