From 2fc4b4c38f9210ec5290aa41d07eaf15a6257152 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 8 May 2026 18:04:34 +0100 Subject: [PATCH] test: require provider optional hooks --- extensions/deepseek/index.test.ts | 13 ++++- extensions/github-copilot/embeddings.test.ts | 26 +++++----- extensions/minimax/speech-provider.test.ts | 54 ++++++++++++++------ 3 files changed, 63 insertions(+), 30 deletions(-) diff --git a/extensions/deepseek/index.test.ts b/extensions/deepseek/index.test.ts index 75f0afa18f1..bd41e7eb650 100644 --- a/extensions/deepseek/index.test.ts +++ b/extensions/deepseek/index.test.ts @@ -16,6 +16,8 @@ type PayloadCapture = { payload?: Record; }; +type RegisteredProvider = Awaited>; + const emptyUsage = { input: 0, output: 0, @@ -25,6 +27,15 @@ const emptyUsage = { cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, }; +function requireThinkingProfileResolver( + provider: RegisteredProvider, +): NonNullable { + if (!provider.resolveThinkingProfile) { + throw new Error("DeepSeek provider did not register a thinking profile resolver"); + } + return provider.resolveThinkingProfile; +} + const readToolCall = { type: "toolCall", id: "call_1", name: "read", arguments: {} }; const readToolResult = { role: "toolResult", @@ -190,7 +201,7 @@ describe("deepseek provider plugin", () => { it("advertises max thinking levels for DeepSeek V4 models only", async () => { const provider = await registerSingleProviderPlugin(deepseekPlugin); - const resolveThinkingProfile = provider.resolveThinkingProfile!; + const resolveThinkingProfile = requireThinkingProfileResolver(provider); const expectedV4Levels = ["off", "minimal", "low", "medium", "high", "xhigh", "max"]; expect( diff --git a/extensions/github-copilot/embeddings.test.ts b/extensions/github-copilot/embeddings.test.ts index b8f3f7beb85..3cfa5b32cf8 100644 --- a/extensions/github-copilot/embeddings.test.ts +++ b/extensions/github-copilot/embeddings.test.ts @@ -34,6 +34,14 @@ afterAll(() => { const TEST_BASE_URL = "https://api.githubcopilot.test"; +function shouldContinueAutoSelection(error: Error): boolean { + const shouldContinue = githubCopilotMemoryEmbeddingProviderAdapter.shouldContinueAutoSelection; + if (!shouldContinue) { + throw new Error("GitHub Copilot embedding adapter did not expose auto-selection fallback"); + } + return shouldContinue(error); +} + function buildModelsResponse(models: Array<{ id: string; supported_endpoints?: unknown }>) { return { data: models }; } @@ -242,25 +250,19 @@ describe("githubCopilotMemoryEmbeddingProviderAdapter", () => { }); it("treats token parsing and discovery failures as auto-fallback errors", () => { + expect(shouldContinueAutoSelection(new Error("Copilot token response missing token"))).toBe( + true, + ); expect( - githubCopilotMemoryEmbeddingProviderAdapter.shouldContinueAutoSelection!( - new Error("Copilot token response missing token"), - ), - ).toBe(true); - expect( - githubCopilotMemoryEmbeddingProviderAdapter.shouldContinueAutoSelection!( + shouldContinueAutoSelection( new Error("Unexpected response from GitHub Copilot token endpoint"), ), ).toBe(true); expect( - githubCopilotMemoryEmbeddingProviderAdapter.shouldContinueAutoSelection!( + shouldContinueAutoSelection( new Error("GitHub Copilot model discovery returned invalid JSON"), ), ).toBe(true); - expect( - githubCopilotMemoryEmbeddingProviderAdapter.shouldContinueAutoSelection!( - new Error("Network timeout"), - ), - ).toBe(false); + expect(shouldContinueAutoSelection(new Error("Network timeout"))).toBe(false); }); }); diff --git a/extensions/minimax/speech-provider.test.ts b/extensions/minimax/speech-provider.test.ts index 96087adf1ac..e8ea8969138 100644 --- a/extensions/minimax/speech-provider.test.ts +++ b/extensions/minimax/speech-provider.test.ts @@ -21,6 +21,26 @@ function clearMinimaxAuthEnv() { describe("buildMinimaxSpeechProvider", () => { const provider = buildMinimaxSpeechProvider(); + function resolveProviderConfig( + params: Parameters>[0], + ): ReturnType> { + const resolveConfig = provider.resolveConfig; + if (!resolveConfig) { + throw new Error("MiniMax speech provider did not expose config resolution"); + } + return resolveConfig(params); + } + + function parseDirectiveToken( + params: Parameters>[0], + ): ReturnType> { + const parseToken = provider.parseDirectiveToken; + if (!parseToken) { + throw new Error("MiniMax speech provider did not expose directive parsing"); + } + return parseToken(params); + } + describe("metadata", () => { it("has correct id and label", () => { expect(provider.id).toBe("minimax"); @@ -107,14 +127,14 @@ describe("buildMinimaxSpeechProvider", () => { delete process.env.MINIMAX_API_HOST; delete process.env.MINIMAX_TTS_MODEL; delete process.env.MINIMAX_TTS_VOICE_ID; - const config = provider.resolveConfig!({ rawConfig: {}, cfg: {} as never, timeoutMs: 30000 }); + const config = resolveProviderConfig({ rawConfig: {}, cfg: {} as never, timeoutMs: 30000 }); expect(config.baseUrl).toBe("https://api.minimax.io"); expect(config.model).toBe("speech-2.8-hd"); expect(config.voiceId).toBe("English_expressive_narrator"); }); it("reads from providers.minimax in rawConfig", () => { - const config = provider.resolveConfig!({ + const config = resolveProviderConfig({ rawConfig: { providers: { minimax: { @@ -142,7 +162,7 @@ describe("buildMinimaxSpeechProvider", () => { process.env.MINIMAX_API_HOST = "https://api.minimax.io/anthropic"; process.env.MINIMAX_TTS_MODEL = "speech-01-240228"; process.env.MINIMAX_TTS_VOICE_ID = "Chinese (Mandarin)_Gentle_Boy"; - const config = provider.resolveConfig!({ rawConfig: {}, cfg: {} as never, timeoutMs: 30000 }); + const config = resolveProviderConfig({ rawConfig: {}, cfg: {} as never, timeoutMs: 30000 }); expect(config.baseUrl).toBe("https://api.minimax.io"); expect(config.model).toBe("speech-01-240228"); expect(config.voiceId).toBe("Chinese (Mandarin)_Gentle_Boy"); @@ -150,7 +170,7 @@ describe("buildMinimaxSpeechProvider", () => { it("derives the TTS host from minimax-portal OAuth config", () => { delete process.env.MINIMAX_API_HOST; - const config = provider.resolveConfig!({ + const config = resolveProviderConfig({ rawConfig: {}, cfg: { models: { @@ -178,7 +198,7 @@ describe("buildMinimaxSpeechProvider", () => { }; it("handles voice key", () => { - const result = provider.parseDirectiveToken!({ + const result = parseDirectiveToken({ key: "voice", value: "Chinese (Mandarin)_Warm_Girl", policy, @@ -188,13 +208,13 @@ describe("buildMinimaxSpeechProvider", () => { }); it("handles voiceid key", () => { - const result = provider.parseDirectiveToken!({ key: "voiceid", value: "test_voice", policy }); + const result = parseDirectiveToken({ key: "voiceid", value: "test_voice", policy }); expect(result.handled).toBe(true); expect(result.overrides?.voiceId).toBe("test_voice"); }); it("handles model key", () => { - const result = provider.parseDirectiveToken!({ + const result = parseDirectiveToken({ key: "model", value: "speech-01-240228", policy, @@ -204,50 +224,50 @@ describe("buildMinimaxSpeechProvider", () => { }); it("handles speed key with valid value", () => { - const result = provider.parseDirectiveToken!({ key: "speed", value: "1.5", policy }); + const result = parseDirectiveToken({ key: "speed", value: "1.5", policy }); expect(result.handled).toBe(true); expect(result.overrides?.speed).toBe(1.5); }); it("warns on invalid speed", () => { - const result = provider.parseDirectiveToken!({ key: "speed", value: "5.0", policy }); + const result = parseDirectiveToken({ key: "speed", value: "5.0", policy }); expect(result.handled).toBe(true); expect(result.warnings).toHaveLength(1); expect(result.overrides).toBeUndefined(); }); it("handles vol key", () => { - const result = provider.parseDirectiveToken!({ key: "vol", value: "3", policy }); + const result = parseDirectiveToken({ key: "vol", value: "3", policy }); expect(result.handled).toBe(true); expect(result.overrides?.vol).toBe(3); }); it("warns on vol=0 (exclusive minimum)", () => { - const result = provider.parseDirectiveToken!({ key: "vol", value: "0", policy }); + const result = parseDirectiveToken({ key: "vol", value: "0", policy }); expect(result.handled).toBe(true); expect(result.warnings).toHaveLength(1); }); it("handles volume alias", () => { - const result = provider.parseDirectiveToken!({ key: "volume", value: "5", policy }); + const result = parseDirectiveToken({ key: "volume", value: "5", policy }); expect(result.handled).toBe(true); expect(result.overrides?.vol).toBe(5); }); it("handles pitch key", () => { - const result = provider.parseDirectiveToken!({ key: "pitch", value: "-3", policy }); + const result = parseDirectiveToken({ key: "pitch", value: "-3", policy }); expect(result.handled).toBe(true); expect(result.overrides?.pitch).toBe(-3); }); it("warns on out-of-range pitch", () => { - const result = provider.parseDirectiveToken!({ key: "pitch", value: "20", policy }); + const result = parseDirectiveToken({ key: "pitch", value: "20", policy }); expect(result.handled).toBe(true); expect(result.warnings).toHaveLength(1); }); it("returns handled=false for unknown keys", () => { - const result = provider.parseDirectiveToken!({ + const result = parseDirectiveToken({ key: "unknown_key", value: "whatever", policy, @@ -256,7 +276,7 @@ describe("buildMinimaxSpeechProvider", () => { }); it("suppresses voice when policy disallows it", () => { - const result = provider.parseDirectiveToken!({ + const result = parseDirectiveToken({ key: "voice", value: "test", policy: { ...policy, allowVoice: false }, @@ -266,7 +286,7 @@ describe("buildMinimaxSpeechProvider", () => { }); it("suppresses model when policy disallows it", () => { - const result = provider.parseDirectiveToken!({ + const result = parseDirectiveToken({ key: "model", value: "test", policy: { ...policy, allowModelId: false },