| 1 | import { |
| 2 | formatProviderExtraBody, |
| 3 | normalizeProviderView, |
| 4 | parseProviderExtraBody, |
| 5 | providerEditorEffectiveKind, |
| 6 | providerExtraBodyParseError, |
| 7 | } from "../components/SettingsPanel"; |
| 8 | import type { ProviderView } from "../lib/types"; |
| 9 | |
| 10 | let passed = 0; |
| 11 | let failed = 0; |
| 12 | function ok(value: boolean, label: string) { |
| 13 | process.stdout.write(` ${value ? "PASS" : "FAIL"} ${label}\n`); |
| 14 | if (value) passed += 1; else failed += 1; |
| 15 | } |
| 16 | function eq(actual: unknown, expected: unknown, label: string) { |
| 17 | ok(actual === expected, actual === expected ? label : `${label}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`); |
| 18 | } |
| 19 | |
| 20 | console.log("\nsettings provider normalization"); |
| 21 | const nullable = normalizeProviderView({ name: null, baseUrl: null } as unknown as ProviderView); |
| 22 | eq(nullable.name, "", "provider snapshots normalize a null name at the settings boundary"); |
| 23 | eq(nullable.baseUrl, "", "provider snapshots normalize a null base URL at the settings boundary"); |
| 24 | eq(normalizeProviderView({ name: "glm", baseUrl: "https://gateway.example/v1", reasoningProtocol: "glm" } as ProviderView).reasoningProtocol, "glm", "provider snapshots preserve the GLM protocol"); |
| 25 | eq(normalizeProviderView({ name: "anthropic", kind: "anthropic", baseUrl: "https://gateway.example", serverWebSearchCapability: true } as ProviderView).serverWebSearchCapability, true, "provider snapshots preserve server web-search capability"); |
| 26 | eq(normalizeProviderView({ name: "legacy", kind: "anthropic", baseUrl: "https://gateway.example" } as ProviderView).serverWebSearchCapability, undefined, "older snapshots keep an absent capability distinguishable"); |
| 27 | eq(providerEditorEffectiveKind(true, "anthropic", ["anthropic", "openai"]), "anthropic", "new custom providers keep the selected kind"); |
| 28 | eq(providerEditorEffectiveKind(false, "anthropic", ["anthropic", "openai"]), "anthropic", "existing providers preserve their stored kind"); |
| 29 | eq(formatProviderExtraBody({ top_p: 0.7, enable_thinking: true }), "{\n \"enable_thinking\": true,\n \"top_p\": 0.7\n}", "extra body editor formats stable JSON"); |
| 30 | eq(JSON.stringify(parseProviderExtraBody('{ "enable_thinking": true, "top_p": 0.7 }')), '{"enable_thinking":true,"top_p":0.7}', "extra body editor parses an object"); |
| 31 | let rejected = false; |
| 32 | try { parseProviderExtraBody("[true]"); } catch { rejected = true; } |
| 33 | ok(rejected, "extra body editor rejects non-object JSON"); |
| 34 | const t = ((key: string, vars?: Record<string, string | number>) => key === "settings.providerExtraBodyNull" ? `${vars?.path} localized null` : key === "settings.providerExtraBodyError" ? "localized fallback" : key) as any; |
| 35 | eq(providerExtraBodyParseError(new SyntaxError("bad JSON"), t), "localized fallback", "extra body editor localizes syntax errors"); |
| 36 | try { |
| 37 | parseProviderExtraBody('{ "nested": { "value": null } }', t); |
| 38 | ok(false, "extra body editor rejects null values"); |
| 39 | } catch (error) { |
| 40 | eq(providerExtraBodyParseError(error, t), "extra_body.nested.value localized null", "extra body editor retains the structured validation path"); |
| 41 | } |
| 42 | |
| 43 | console.log(`\n${passed} passed, ${failed} failed`); |
| 44 | if (failed) process.exit(1); |
| 45 |