test(e2e): fill the provider cassettes with the shape the backend emits

The settings-providers cassettes left nine of eleven default configs null and
every model catalog empty. Verified against the live backend that this is a
shape it never sends: gemini/deepseek/ollama all return a non-null default and a
populated model catalog even with zero keys configured. The empty catalogs made
the create-form's model-dropdown seeding path structurally unreachable — no spec
could reach it.

Give every type a default config and a two-entry catalog, and add a spec that
picks a type and opens an agent's model dropdown: it asserts a catalog-only
model appears there. That option is gone when the catalog is empty, so the spec
fails on the old backend-impossible shape — the coverage that was missing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Sergey Kozyrenko
2026-07-25 03:45:20 +07:00
co-authored by Claude Opus 4.8
parent b4f50ba3f0
commit 0d4232a1d5
2 changed files with 71 additions and 53 deletions
@@ -3,6 +3,7 @@ import type { ResultOf } from '@graphql-typed-document-node/core';
import type {
AgentConfigFragmentFragment,
AgentsConfigFragmentFragment,
ModelConfigFragmentFragment,
ProviderConfigFragmentFragment,
SettingsProvidersDocument,
} from '@/graphql/types';
@@ -66,21 +67,48 @@ const defaultConfig = (type: ProviderType): ProviderConfigFragmentFragment =>
updatedAt: T,
});
const modelConfig = (name: string): ModelConfigFragmentFragment =>
entity('ModelConfig', { name, price: null, reasoning: null, thinking: null });
// The backend fills a default config AND a model catalog for EVERY type, keyed or not (verified live:
// gemini/deepseek/ollama all return a non-null default and a populated catalog with zero keys). A
// cassette leaving them null/[] encodes a wire state the resolver never emits and leaves the
// create-form's model-dropdown seeding path structurally unreachable.
const allDefaults = () =>
entity('DefaultProvidersConfig', {
anthropic: defaultConfig(ProviderType.Anthropic),
bedrock: defaultConfig(ProviderType.Bedrock),
custom: defaultConfig(ProviderType.Custom),
deepseek: defaultConfig(ProviderType.Deepseek),
gemini: defaultConfig(ProviderType.Gemini),
glm: defaultConfig(ProviderType.Glm),
kimi: defaultConfig(ProviderType.Kimi),
minimax: defaultConfig(ProviderType.Minimax),
ollama: defaultConfig(ProviderType.Ollama),
openai: defaultConfig(ProviderType.Openai),
qwen: defaultConfig(ProviderType.Qwen),
});
const catalog = (type: ProviderType) => [modelConfig(`e2e-${type}-model`), modelConfig(`e2e-${type}-model-mini`)];
const allModels = () =>
entity('ProvidersModelsList', {
anthropic: catalog(ProviderType.Anthropic),
bedrock: catalog(ProviderType.Bedrock),
custom: catalog(ProviderType.Custom),
deepseek: catalog(ProviderType.Deepseek),
gemini: catalog(ProviderType.Gemini),
glm: catalog(ProviderType.Glm),
kimi: catalog(ProviderType.Kimi),
minimax: catalog(ProviderType.Minimax),
ollama: catalog(ProviderType.Ollama),
openai: catalog(ProviderType.Openai),
qwen: catalog(ProviderType.Qwen),
});
const noProviders: ResultOf<typeof SettingsProvidersDocument> = {
settingsProviders: entity('ProvidersConfig', {
default: entity('DefaultProvidersConfig', {
anthropic: defaultConfig(ProviderType.Anthropic),
bedrock: null,
custom: null,
deepseek: null,
gemini: null,
glm: null,
kimi: null,
minimax: null,
ollama: null,
openai: defaultConfig(ProviderType.Openai),
qwen: null,
}),
default: allDefaults(),
enabled: entity('ProvidersReadinessStatus', {
anthropic: false,
bedrock: false,
@@ -94,19 +122,7 @@ const noProviders: ResultOf<typeof SettingsProvidersDocument> = {
openai: false,
qwen: false,
}),
models: entity('ProvidersModelsList', {
anthropic: [],
bedrock: [],
custom: [],
deepseek: [],
gemini: [],
glm: [],
kimi: [],
minimax: [],
ollama: [],
openai: [],
qwen: [],
}),
models: allModels(),
userDefined: [],
}),
};
@@ -125,19 +141,7 @@ export const settingsProvidersCassette = (override: Cassette = {}): Cassette =>
const populatedProviders: ResultOf<typeof SettingsProvidersDocument> = {
settingsProviders: entity('ProvidersConfig', {
default: entity('DefaultProvidersConfig', {
anthropic: defaultConfig(ProviderType.Anthropic),
bedrock: null,
custom: null,
deepseek: null,
gemini: null,
glm: null,
kimi: null,
minimax: null,
ollama: null,
openai: defaultConfig(ProviderType.Openai),
qwen: null,
}),
default: allDefaults(),
enabled: entity('ProvidersReadinessStatus', {
anthropic: true,
bedrock: false,
@@ -151,19 +155,7 @@ const populatedProviders: ResultOf<typeof SettingsProvidersDocument> = {
openai: true,
qwen: false,
}),
models: entity('ProvidersModelsList', {
anthropic: [],
bedrock: [],
custom: [],
deepseek: [],
gemini: [],
glm: [],
kimi: [],
minimax: [],
ollama: [],
openai: [],
qwen: [],
}),
models: allModels(),
userDefined: [
entity('ProviderConfig', {
agents: agentsConfig(),
+27 -1
View File
@@ -1,6 +1,9 @@
import { expect, test } from '../../fixtures/test.ts';
import { expectCleanPage } from '../../helpers/errors.ts';
import { settingsProvidersCassette } from '../../mocks/cassettes/settings-providers.ts';
import {
populatedSettingsProvidersCassette,
settingsProvidersCassette,
} from '../../mocks/cassettes/settings-providers.ts';
test.describe('settings providers', { tag: '@coverage' }, () => {
test.use({ cassette: settingsProvidersCassette() });
@@ -27,3 +30,26 @@ test.describe('settings providers', { tag: '@coverage' }, () => {
expectCleanPage(pageErrorLog);
});
});
test.describe('settings providers create form', { tag: '@coverage' }, () => {
// Needs a cassette where the type is enabled: the create form bounces `?type=` for a disabled
// one, so the empty seed would just redirect to the list.
test.use({ cassette: populatedSettingsProvidersCassette() });
// A selected type seeds each agent's model from that type's catalog. The catalog must carry the
// shape the backend actually emits (a non-empty model list for every type, keyed or not); an
// empty catalog makes this whole path unreachable and any assertion on it vacuous. `?type=` is
// the URL the type picker produces, driven directly to avoid the dirty-form navigation blocker.
test('seeds the agent model from the selected type catalog', async ({ page, pageErrorLog }) => {
await page.goto('/settings/providers/new?type=anthropic');
await page.getByRole('button', { name: /^Adviser/ }).click();
await page.getByRole('button', { name: 'Open model list' }).first().click();
// A catalog-only model (not the seeded agent default), so it can only appear if the type's
// model list reached the dropdown — the path an empty catalog leaves unreachable.
await expect(page.getByRole('option', { name: 'e2e-anthropic-model-mini' })).toBeVisible();
expectCleanPage(pageErrorLog);
});
});