test(e2e): serve default providers the id the backend actually sends

The resolver builds default provider configs without an ID, so gqlgen marshals
the bare number 0 for every one of them. The cassette handed each a distinct
string id instead, which left `ProviderConfig.keyFields`'s id-0 branch — the one
that stops those un-normalisable defaults collapsing onto a single cache entry —
unexercised by any test.

Serve id 0 and the plain provider name, give each default a distinguishable
model so a collapse is observable, and pin the cache contract directly: with the
keyFields branch removed the new test reads anthropic's default as openai's.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Sergey Kozyrenko
2026-07-24 18:40:24 +07:00
co-authored by Claude Opus 4.8
parent c8314c4213
commit a51e36fdc7
2 changed files with 79 additions and 24 deletions
@@ -16,14 +16,14 @@ import { baseQueries, baseRest } from './base.ts';
const T = '2026-01-15T09:00:00Z';
const agentConfig = (): AgentConfigFragmentFragment =>
const agentConfig = (model = 'e2e-model'): AgentConfigFragmentFragment =>
entity('AgentConfig', {
extraBody: null,
frequencyPenalty: null,
maxLength: null,
maxTokens: null,
minLength: null,
model: 'e2e-model',
model,
presencePenalty: null,
price: null,
reasoning: null,
@@ -33,29 +33,35 @@ const agentConfig = (): AgentConfigFragmentFragment =>
topP: null,
});
const agentsConfig = (): AgentsConfigFragmentFragment =>
const agentsConfig = (model?: string): AgentsConfigFragmentFragment =>
entity('AgentsConfig', {
adviser: agentConfig(),
assistant: agentConfig(),
coder: agentConfig(),
enricher: agentConfig(),
generator: agentConfig(),
installer: agentConfig(),
pentester: agentConfig(),
primaryAgent: agentConfig(),
refiner: agentConfig(),
reflector: agentConfig(),
searcher: agentConfig(),
simple: agentConfig(),
simpleJson: agentConfig(),
adviser: agentConfig(model),
assistant: agentConfig(model),
coder: agentConfig(model),
enricher: agentConfig(model),
generator: agentConfig(model),
installer: agentConfig(model),
pentester: agentConfig(model),
primaryAgent: agentConfig(model),
refiner: agentConfig(model),
reflector: agentConfig(model),
searcher: agentConfig(model),
simple: agentConfig(model),
simpleJson: agentConfig(model),
});
const defaultConfig = (id: string, type: ProviderType): ProviderConfigFragmentFragment =>
/**
* Defaults are not database rows: the resolver builds them without an ID, so the wire carries the
* bare number `0` for every one. `ProviderConfig.keyFields`'s id-0 branch exists for that shape, and
* a distinct string id here leaves it unexercised.
*/
const defaultConfig = (type: ProviderType): ProviderConfigFragmentFragment =>
entity('ProviderConfig', {
agents: agentsConfig(),
// Distinct per type, so two defaults collapsing onto one cache entry is observable.
agents: agentsConfig(`e2e-${type}-model`),
createdAt: T,
id,
name: `default-${type}`,
id: 0 as unknown as string,
name: type,
type,
updatedAt: T,
});
@@ -63,7 +69,7 @@ const defaultConfig = (id: string, type: ProviderType): ProviderConfigFragmentFr
const noProviders: ResultOf<typeof SettingsProvidersDocument> = {
settingsProviders: entity('ProvidersConfig', {
default: entity('DefaultProvidersConfig', {
anthropic: defaultConfig('default-anthropic', ProviderType.Anthropic),
anthropic: defaultConfig(ProviderType.Anthropic),
bedrock: null,
custom: null,
deepseek: null,
@@ -72,7 +78,7 @@ const noProviders: ResultOf<typeof SettingsProvidersDocument> = {
kimi: null,
minimax: null,
ollama: null,
openai: defaultConfig('default-openai', ProviderType.Openai),
openai: defaultConfig(ProviderType.Openai),
qwen: null,
}),
enabled: entity('ProvidersReadinessStatus', {
@@ -120,7 +126,7 @@ export const settingsProvidersCassette = (override: Cassette = {}): Cassette =>
const populatedProviders: ResultOf<typeof SettingsProvidersDocument> = {
settingsProviders: entity('ProvidersConfig', {
default: entity('DefaultProvidersConfig', {
anthropic: defaultConfig('default-anthropic', ProviderType.Anthropic),
anthropic: defaultConfig(ProviderType.Anthropic),
bedrock: null,
custom: null,
deepseek: null,
@@ -129,7 +135,7 @@ const populatedProviders: ResultOf<typeof SettingsProvidersDocument> = {
kimi: null,
minimax: null,
ollama: null,
openai: defaultConfig('default-openai', ProviderType.Openai),
openai: defaultConfig(ProviderType.Openai),
qwen: null,
}),
enabled: entity('ProvidersReadinessStatus', {
+49
View File
@@ -284,3 +284,52 @@ describe('streaming assistant-log link (createStreamingLink)', () => {
expect(emitted).toEqual([{ somethingElse: { id: '1' } }]);
});
});
describe('ProviderConfig cache identity', () => {
const PROVIDERS = gql`
query P {
settingsProviders {
default {
anthropic {
id
name
type
}
openai {
id
name
type
}
}
}
}
`;
// The resolver builds default providers without an ID, so every one of them arrives as id 0.
// Normalising on that shared id would collapse them onto a single cache entry and the last
// write would win — the create form then seeds one provider type from another's defaults.
it('keeps two defaults apart even though both carry id 0', () => {
const cache = createCache();
cache.writeQuery({
data: {
settingsProviders: {
__typename: 'ProvidersConfig',
default: {
__typename: 'DefaultProvidersConfig',
anthropic: { __typename: 'ProviderConfig', id: 0, name: 'anthropic', type: 'anthropic' },
openai: { __typename: 'ProviderConfig', id: 0, name: 'openai', type: 'openai' },
},
},
},
query: PROVIDERS,
});
const read = cache.readQuery<{
settingsProviders: { default: { anthropic: { name: string }; openai: { name: string } } };
}>({ query: PROVIDERS });
expect(read?.settingsProviders.default.anthropic.name).toBe('anthropic');
expect(read?.settingsProviders.default.openai.name).toBe('openai');
});
});