Skip to content
This repository was archived by the owner on May 20, 2026. It is now read-only.

Commit 61bc734

Browse files
author
jliounis
committed
test(byok/perplexity): add unit tests for curated list + integration header
Three cases: - getAllModels returns the curated Agent API list with the correct base URL and asserts no sonar references remain. - every model carries the X-Pplx-Integration header in merged capabilities. - per-model requestHeaders cannot override the integration header.
1 parent 16b00ef commit 61bc734

1 file changed

Lines changed: 125 additions & 0 deletions

File tree

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { describe, expect, it, vi } from 'vitest';
7+
import { PerplexityLMProvider } from '../perplexityProvider';
8+
9+
function createProvider(knownModels?: Record<string, any>) {
10+
const fetch = vi.fn(async (url: string) => {
11+
throw new Error(`Unexpected fetch in test: ${url}`);
12+
});
13+
14+
const logService = {
15+
_serviceBrand: undefined,
16+
trace: vi.fn(),
17+
debug: vi.fn(),
18+
info: vi.fn(),
19+
warn: vi.fn(),
20+
error: vi.fn(),
21+
show: vi.fn(),
22+
createSubLogger: vi.fn(),
23+
withExtraTarget: vi.fn(),
24+
};
25+
logService.createSubLogger.mockReturnValue(logService);
26+
logService.withExtraTarget.mockReturnValue(logService);
27+
28+
const storage = {
29+
getAPIKey: vi.fn().mockResolvedValue(undefined),
30+
storeAPIKey: vi.fn().mockResolvedValue(undefined),
31+
deleteAPIKey: vi.fn().mockResolvedValue(undefined),
32+
getStoredModelConfigs: vi.fn().mockResolvedValue({}),
33+
saveModelConfig: vi.fn().mockResolvedValue(undefined),
34+
removeModelConfig: vi.fn().mockResolvedValue(undefined),
35+
};
36+
37+
const provider = new PerplexityLMProvider(
38+
knownModels as any,
39+
storage as any,
40+
{ fetch } as any,
41+
logService as any,
42+
{ createInstance: vi.fn().mockReturnValue({}) } as any,
43+
{
44+
isConfigured: vi.fn().mockReturnValue(false),
45+
getConfig: vi.fn(),
46+
setConfig: vi.fn(),
47+
} as any,
48+
{} as any
49+
);
50+
51+
return { provider, fetch, storage, logService };
52+
}
53+
54+
describe('PerplexityLMProvider', () => {
55+
it('getAllModels returns the curated Agent API model list', async () => {
56+
const { provider, fetch } = createProvider();
57+
58+
const models = await (provider as any).getAllModels(true, 'test-api-key', undefined);
59+
60+
expect(models.length).toBe(5);
61+
62+
const expectedIds = [
63+
'openai/gpt-5.4',
64+
'openai/gpt-5.2',
65+
'anthropic/claude-sonnet-4-6',
66+
'anthropic/claude-opus-4-7',
67+
'google/gemini-3-1-pro',
68+
];
69+
const ids = models.map((m: any) => m.id);
70+
expect(ids.sort()).toEqual(expectedIds.sort());
71+
72+
// No sonar references at all in the curated list.
73+
expect(models.some((m: any) => m.id.includes('sonar'))).toBe(false);
74+
expect(models.some((m: any) => (m.name ?? '').toLowerCase().includes('sonar'))).toBe(false);
75+
76+
// Each model points at the Agent API base URL.
77+
for (const model of models) {
78+
expect(model.url).toBe('https://api.perplexity.ai/v1');
79+
}
80+
81+
// /models endpoint must never be queried — getAllModels is overridden.
82+
expect(fetch).not.toHaveBeenCalled();
83+
});
84+
85+
it('every model includes X-Pplx-Integration header in merged capabilities', async () => {
86+
const { provider } = createProvider();
87+
88+
const knownModels = (provider as any)._knownModels as Record<string, { requestHeaders?: Record<string, string> }>;
89+
expect(knownModels).toBeDefined();
90+
const ids = Object.keys(knownModels);
91+
expect(ids.length).toBe(5);
92+
93+
for (const id of ids) {
94+
const headers = knownModels[id].requestHeaders;
95+
expect(headers).toBeDefined();
96+
expect(headers!['X-Pplx-Integration']).toMatch(/^vscode-copilot\//);
97+
}
98+
});
99+
100+
it('integration header is not overridden by per-model requestHeaders', () => {
101+
const { provider } = createProvider({
102+
'openai/gpt-5.4': {
103+
name: 'GPT-5.4 (custom)',
104+
toolCalling: true,
105+
vision: true,
106+
maxInputTokens: 200000,
107+
maxOutputTokens: 16000,
108+
requestHeaders: {
109+
'X-Pplx-Integration': 'malicious-override',
110+
'X-Custom-Header': 'kept',
111+
},
112+
},
113+
});
114+
115+
const knownModels = (provider as any)._knownModels as Record<string, { requestHeaders?: Record<string, string> }>;
116+
const headers = knownModels['openai/gpt-5.4'].requestHeaders!;
117+
118+
// The provider's integration header always wins.
119+
expect(headers['X-Pplx-Integration']).toMatch(/^vscode-copilot\//);
120+
expect(headers['X-Pplx-Integration']).not.toBe('malicious-override');
121+
122+
// Other custom headers should be preserved.
123+
expect(headers['X-Custom-Header']).toBe('kept');
124+
});
125+
});

0 commit comments

Comments
 (0)