GET/PATCH /api/providers/config, subset POST /refresh, and
GET /api/providers/:id/diagnostic (JSON { diagnostic }, §6.4). PATCH order
is validate→save→reload→clear; a malformed body or invalid merged config
returns 422 without writing, and a save failure returns 500 without
reloading (no file/registry divergence). Web client + types extended.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
97 lines
3.7 KiB
TypeScript
97 lines
3.7 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
mergeProviderConfigPatch,
|
|
ProviderConfigPatchSchema,
|
|
CoderProvidersFileSchema,
|
|
type CoderProvidersFile,
|
|
} from '../provider-config.js';
|
|
|
|
describe('ProviderConfigPatchSchema', () => {
|
|
it('accepts a per-provider override patch', () => {
|
|
const parsed = ProviderConfigPatchSchema.safeParse({ providers: { goose: { enabled: false } } });
|
|
expect(parsed.success).toBe(true);
|
|
});
|
|
|
|
it('accepts a null value (delete-the-override sentinel)', () => {
|
|
const parsed = ProviderConfigPatchSchema.safeParse({ providers: { goose: null } });
|
|
expect(parsed.success).toBe(true);
|
|
});
|
|
|
|
it('defaults providers to {} on an empty body', () => {
|
|
const parsed = ProviderConfigPatchSchema.safeParse({});
|
|
expect(parsed.success).toBe(true);
|
|
if (parsed.success) expect(parsed.data.providers).toEqual({});
|
|
});
|
|
|
|
it('rejects a malformed override (wrong field type)', () => {
|
|
const parsed = ProviderConfigPatchSchema.safeParse({ providers: { goose: { enabled: 'yes' } } });
|
|
expect(parsed.success).toBe(false);
|
|
});
|
|
|
|
it('rejects a non-object providers map', () => {
|
|
const parsed = ProviderConfigPatchSchema.safeParse({ providers: 123 });
|
|
expect(parsed.success).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('mergeProviderConfigPatch', () => {
|
|
const current: CoderProvidersFile = {
|
|
providers: {
|
|
goose: { enabled: true, label: 'Goose' },
|
|
opencode: { enabled: true },
|
|
},
|
|
};
|
|
|
|
it('replaces an existing override object wholesale (not deep-merge)', () => {
|
|
const merged = mergeProviderConfigPatch(current, { providers: { goose: { enabled: false } } });
|
|
// Whole override replaced — the prior `label` is gone, only `enabled` remains.
|
|
expect(merged.providers.goose).toEqual({ enabled: false });
|
|
});
|
|
|
|
it('adds a brand-new override id', () => {
|
|
const merged = mergeProviderConfigPatch(current, {
|
|
providers: { 'amp-acp': { extends: 'acp', label: 'Amp', command: ['amp-acp'] } },
|
|
});
|
|
expect(merged.providers['amp-acp']).toEqual({ extends: 'acp', label: 'Amp', command: ['amp-acp'] });
|
|
});
|
|
|
|
it('deletes an override when the value is null', () => {
|
|
const merged = mergeProviderConfigPatch(current, { providers: { goose: null } });
|
|
expect(merged.providers.goose).toBeUndefined();
|
|
expect(Object.keys(merged.providers)).toEqual(['opencode']);
|
|
});
|
|
|
|
it('leaves ids absent from the patch untouched', () => {
|
|
const merged = mergeProviderConfigPatch(current, { providers: { goose: { enabled: false } } });
|
|
expect(merged.providers.opencode).toEqual({ enabled: true });
|
|
});
|
|
|
|
it('does not mutate the input config', () => {
|
|
const snapshot = JSON.parse(JSON.stringify(current));
|
|
mergeProviderConfigPatch(current, { providers: { goose: null, opencode: { enabled: false } } });
|
|
expect(current).toEqual(snapshot);
|
|
});
|
|
|
|
it('empty patch returns an equivalent config', () => {
|
|
const merged = mergeProviderConfigPatch(current, { providers: {} });
|
|
expect(merged).toEqual(current);
|
|
});
|
|
});
|
|
|
|
describe('CoderProvidersFileSchema (validate-before-save guard)', () => {
|
|
it('accepts a clean merged config', () => {
|
|
const merged = mergeProviderConfigPatch(
|
|
{ providers: {} },
|
|
{ providers: { goose: { enabled: false } } },
|
|
);
|
|
expect(CoderProvidersFileSchema.safeParse(merged).success).toBe(true);
|
|
});
|
|
|
|
it('rejects a config carrying an invalid override (never written)', () => {
|
|
// A merged object that somehow holds a bad override must fail validation
|
|
// so the PATCH route returns 422 and never calls save().
|
|
const invalid = { providers: { goose: { enabled: 'nope' } } };
|
|
expect(CoderProvidersFileSchema.safeParse(invalid).success).toBe(false);
|
|
});
|
|
});
|