Skip to content

Commit 4b73151

Browse files
committed
test: add unit tests for ProviderConfig and AzureOptions classes
1 parent cd72693 commit 4b73151

1 file changed

Lines changed: 389 additions & 0 deletions

File tree

Lines changed: 389 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,389 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
package com.github.copilot.sdk;
6+
7+
import static org.junit.jupiter.api.Assertions.assertEquals;
8+
import static org.junit.jupiter.api.Assertions.assertNotNull;
9+
import static org.junit.jupiter.api.Assertions.assertNull;
10+
import static org.junit.jupiter.api.Assertions.assertTrue;
11+
12+
import org.junit.jupiter.api.Test;
13+
14+
import com.fasterxml.jackson.databind.JsonNode;
15+
import com.fasterxml.jackson.databind.ObjectMapper;
16+
17+
import com.github.copilot.sdk.json.AzureOptions;
18+
import com.github.copilot.sdk.json.ProviderConfig;
19+
import com.github.copilot.sdk.json.ResumeSessionConfig;
20+
import com.github.copilot.sdk.json.SessionConfig;
21+
22+
/**
23+
* Tests for {@link ProviderConfig} and {@link AzureOptions} BYOK (Bring Your
24+
* Own Key) configuration.
25+
*
26+
* <p>
27+
* Covers fluent setters, JSON serialization, null-field omission, and
28+
* integration with {@link SessionConfig} and {@link ResumeSessionConfig}.
29+
* </p>
30+
*/
31+
public class ProviderConfigTest {
32+
33+
private static final ObjectMapper MAPPER = JsonRpcClient.getObjectMapper();
34+
35+
// =========================================================================
36+
// Fluent setters and getters
37+
// =========================================================================
38+
39+
@Test
40+
void testDefaultsAreNull() {
41+
var provider = new ProviderConfig();
42+
43+
assertNull(provider.getType());
44+
assertNull(provider.getWireApi());
45+
assertNull(provider.getBaseUrl());
46+
assertNull(provider.getApiKey());
47+
assertNull(provider.getBearerToken());
48+
assertNull(provider.getAzure());
49+
}
50+
51+
@Test
52+
void testFluentSettersReturnSameInstance() {
53+
var provider = new ProviderConfig();
54+
55+
ProviderConfig result = provider.setType("openai").setWireApi("completions")
56+
.setBaseUrl("https://api.openai.com/v1").setApiKey("sk-test-key").setBearerToken("bearer-token")
57+
.setAzure(new AzureOptions());
58+
59+
// All chained calls should return the same instance
60+
assertEquals(provider, result);
61+
}
62+
63+
@Test
64+
void testGettersReturnSetValues() {
65+
var azure = new AzureOptions().setApiVersion("2024-02-01");
66+
var provider = new ProviderConfig().setType("azure-openai").setWireApi("chat")
67+
.setBaseUrl("https://my-resource.openai.azure.com").setApiKey("my-key").setBearerToken("my-token")
68+
.setAzure(azure);
69+
70+
assertEquals("azure-openai", provider.getType());
71+
assertEquals("chat", provider.getWireApi());
72+
assertEquals("https://my-resource.openai.azure.com", provider.getBaseUrl());
73+
assertEquals("my-key", provider.getApiKey());
74+
assertEquals("my-token", provider.getBearerToken());
75+
assertNotNull(provider.getAzure());
76+
assertEquals("2024-02-01", provider.getAzure().getApiVersion());
77+
}
78+
79+
// =========================================================================
80+
// AzureOptions
81+
// =========================================================================
82+
83+
@Test
84+
void testAzureOptionsDefaultsAreNull() {
85+
var azure = new AzureOptions();
86+
assertNull(azure.getApiVersion());
87+
}
88+
89+
@Test
90+
void testAzureOptionsFluentSetter() {
91+
var azure = new AzureOptions();
92+
AzureOptions result = azure.setApiVersion("2023-12-01-preview");
93+
94+
assertEquals(azure, result);
95+
assertEquals("2023-12-01-preview", azure.getApiVersion());
96+
}
97+
98+
// =========================================================================
99+
// JSON serialization — OpenAI BYOK
100+
// =========================================================================
101+
102+
@Test
103+
void testSerializeOpenAiProvider() throws Exception {
104+
var provider = new ProviderConfig().setType("openai").setBaseUrl("https://api.openai.com/v1")
105+
.setApiKey("sk-test-key");
106+
107+
JsonNode json = MAPPER.valueToTree(provider);
108+
109+
assertEquals("openai", json.get("type").asText());
110+
assertEquals("https://api.openai.com/v1", json.get("baseUrl").asText());
111+
assertEquals("sk-test-key", json.get("apiKey").asText());
112+
// Null fields must be omitted (NON_NULL)
113+
assertTrue(json.path("wireApi").isMissingNode());
114+
assertTrue(json.path("bearerToken").isMissingNode());
115+
assertTrue(json.path("azure").isMissingNode());
116+
}
117+
118+
@Test
119+
void testDeserializeOpenAiProvider() throws Exception {
120+
String json = """
121+
{
122+
"type": "openai",
123+
"baseUrl": "https://api.openai.com/v1",
124+
"apiKey": "sk-test-key"
125+
}
126+
""";
127+
128+
ProviderConfig provider = MAPPER.readValue(json, ProviderConfig.class);
129+
130+
assertEquals("openai", provider.getType());
131+
assertEquals("https://api.openai.com/v1", provider.getBaseUrl());
132+
assertEquals("sk-test-key", provider.getApiKey());
133+
assertNull(provider.getWireApi());
134+
assertNull(provider.getBearerToken());
135+
assertNull(provider.getAzure());
136+
}
137+
138+
// =========================================================================
139+
// JSON serialization — Azure OpenAI BYOK
140+
// =========================================================================
141+
142+
@Test
143+
void testSerializeAzureOpenAiProvider() throws Exception {
144+
var provider = new ProviderConfig().setType("azure-openai").setBaseUrl("https://my-resource.openai.azure.com")
145+
.setApiKey("azure-api-key").setAzure(new AzureOptions().setApiVersion("2024-02-01"));
146+
147+
JsonNode json = MAPPER.valueToTree(provider);
148+
149+
assertEquals("azure-openai", json.get("type").asText());
150+
assertEquals("https://my-resource.openai.azure.com", json.get("baseUrl").asText());
151+
assertEquals("azure-api-key", json.get("apiKey").asText());
152+
assertNotNull(json.get("azure"));
153+
assertEquals("2024-02-01", json.get("azure").get("apiVersion").asText());
154+
}
155+
156+
@Test
157+
void testDeserializeAzureOpenAiProvider() throws Exception {
158+
String json = """
159+
{
160+
"type": "azure-openai",
161+
"baseUrl": "https://my-resource.openai.azure.com",
162+
"apiKey": "azure-key",
163+
"azure": {
164+
"apiVersion": "2024-02-01"
165+
}
166+
}
167+
""";
168+
169+
ProviderConfig provider = MAPPER.readValue(json, ProviderConfig.class);
170+
171+
assertEquals("azure-openai", provider.getType());
172+
assertEquals("https://my-resource.openai.azure.com", provider.getBaseUrl());
173+
assertEquals("azure-key", provider.getApiKey());
174+
assertNotNull(provider.getAzure());
175+
assertEquals("2024-02-01", provider.getAzure().getApiVersion());
176+
}
177+
178+
// =========================================================================
179+
// JSON serialization — Bearer token authentication
180+
// =========================================================================
181+
182+
@Test
183+
void testSerializeBearerTokenProvider() throws Exception {
184+
var provider = new ProviderConfig().setType("openai").setBaseUrl("https://custom-provider.example.com/v1")
185+
.setBearerToken("eyJhbGciOiJSUzI1NiIs...");
186+
187+
JsonNode json = MAPPER.valueToTree(provider);
188+
189+
assertEquals("openai", json.get("type").asText());
190+
assertEquals("https://custom-provider.example.com/v1", json.get("baseUrl").asText());
191+
assertEquals("eyJhbGciOiJSUzI1NiIs...", json.get("bearerToken").asText());
192+
assertTrue(json.path("apiKey").isMissingNode());
193+
}
194+
195+
@Test
196+
void testDeserializeBearerTokenProvider() throws Exception {
197+
String json = """
198+
{
199+
"type": "openai",
200+
"baseUrl": "https://custom-provider.example.com/v1",
201+
"bearerToken": "my-bearer-token"
202+
}
203+
""";
204+
205+
ProviderConfig provider = MAPPER.readValue(json, ProviderConfig.class);
206+
207+
assertEquals("openai", provider.getType());
208+
assertEquals("https://custom-provider.example.com/v1", provider.getBaseUrl());
209+
assertEquals("my-bearer-token", provider.getBearerToken());
210+
assertNull(provider.getApiKey());
211+
}
212+
213+
// =========================================================================
214+
// JSON serialization — custom wire API
215+
// =========================================================================
216+
217+
@Test
218+
void testSerializeCustomWireApi() throws Exception {
219+
var provider = new ProviderConfig().setType("openai").setBaseUrl("https://custom.example.com").setApiKey("key")
220+
.setWireApi("responses");
221+
222+
JsonNode json = MAPPER.valueToTree(provider);
223+
224+
assertEquals("responses", json.get("wireApi").asText());
225+
}
226+
227+
// =========================================================================
228+
// JSON serialization — all fields populated
229+
// =========================================================================
230+
231+
@Test
232+
void testSerializeAllFields() throws Exception {
233+
var provider = new ProviderConfig().setType("azure-openai").setWireApi("completions")
234+
.setBaseUrl("https://my-resource.openai.azure.com").setApiKey("my-api-key")
235+
.setBearerToken("my-bearer-token").setAzure(new AzureOptions().setApiVersion("2024-02-01"));
236+
237+
JsonNode json = MAPPER.valueToTree(provider);
238+
239+
assertEquals("azure-openai", json.get("type").asText());
240+
assertEquals("completions", json.get("wireApi").asText());
241+
assertEquals("https://my-resource.openai.azure.com", json.get("baseUrl").asText());
242+
assertEquals("my-api-key", json.get("apiKey").asText());
243+
assertEquals("my-bearer-token", json.get("bearerToken").asText());
244+
assertEquals("2024-02-01", json.get("azure").get("apiVersion").asText());
245+
assertEquals(6, json.size(), "Expected exactly 6 JSON fields");
246+
}
247+
248+
@Test
249+
void testSerializeEmptyProviderOmitsAllFields() throws Exception {
250+
var provider = new ProviderConfig();
251+
252+
JsonNode json = MAPPER.valueToTree(provider);
253+
254+
assertEquals(0, json.size(), "Empty ProviderConfig should serialize to {}");
255+
}
256+
257+
@Test
258+
void testSerializeEmptyAzureOptionsOmitsAllFields() throws Exception {
259+
var azure = new AzureOptions();
260+
261+
JsonNode json = MAPPER.valueToTree(azure);
262+
263+
assertEquals(0, json.size(), "Empty AzureOptions should serialize to {}");
264+
}
265+
266+
// =========================================================================
267+
// JSON round-trip
268+
// =========================================================================
269+
270+
@Test
271+
void testRoundTripProviderConfig() throws Exception {
272+
var original = new ProviderConfig().setType("azure-openai").setWireApi("completions")
273+
.setBaseUrl("https://my-resource.openai.azure.com").setApiKey("my-key").setBearerToken("my-token")
274+
.setAzure(new AzureOptions().setApiVersion("2024-02-01"));
275+
276+
String json = MAPPER.writeValueAsString(original);
277+
ProviderConfig deserialized = MAPPER.readValue(json, ProviderConfig.class);
278+
279+
assertEquals(original.getType(), deserialized.getType());
280+
assertEquals(original.getWireApi(), deserialized.getWireApi());
281+
assertEquals(original.getBaseUrl(), deserialized.getBaseUrl());
282+
assertEquals(original.getApiKey(), deserialized.getApiKey());
283+
assertEquals(original.getBearerToken(), deserialized.getBearerToken());
284+
assertNotNull(deserialized.getAzure());
285+
assertEquals(original.getAzure().getApiVersion(), deserialized.getAzure().getApiVersion());
286+
}
287+
288+
@Test
289+
void testForwardCompatibilityIgnoresUnknownFields() throws Exception {
290+
String json = """
291+
{
292+
"type": "openai",
293+
"baseUrl": "https://api.openai.com/v1",
294+
"apiKey": "sk-key",
295+
"unknownFutureField": "some-value",
296+
"anotherNewField": 42
297+
}
298+
""";
299+
300+
// Should not throw - ObjectMapper is configured with
301+
// FAIL_ON_UNKNOWN_PROPERTIES = false
302+
ProviderConfig provider = MAPPER.readValue(json, ProviderConfig.class);
303+
304+
assertEquals("openai", provider.getType());
305+
assertEquals("https://api.openai.com/v1", provider.getBaseUrl());
306+
assertEquals("sk-key", provider.getApiKey());
307+
}
308+
309+
// =========================================================================
310+
// Integration with SessionConfig
311+
// =========================================================================
312+
313+
@Test
314+
void testSessionConfigWithOpenAiProvider() throws Exception {
315+
var config = new SessionConfig().setModel("gpt-4").setProvider(new ProviderConfig().setType("openai")
316+
.setBaseUrl("https://api.openai.com/v1").setApiKey("sk-test-key"));
317+
318+
JsonNode json = MAPPER.valueToTree(config);
319+
320+
assertNotNull(json.get("provider"));
321+
assertEquals("openai", json.get("provider").get("type").asText());
322+
assertEquals("https://api.openai.com/v1", json.get("provider").get("baseUrl").asText());
323+
assertEquals("sk-test-key", json.get("provider").get("apiKey").asText());
324+
assertEquals("gpt-4", json.get("model").asText());
325+
}
326+
327+
@Test
328+
void testSessionConfigWithAzureProvider() throws Exception {
329+
var config = new SessionConfig().setModel("gpt-4").setProvider(
330+
new ProviderConfig().setType("azure-openai").setBaseUrl("https://my-resource.openai.azure.com")
331+
.setApiKey("azure-key").setAzure(new AzureOptions().setApiVersion("2024-02-01")));
332+
333+
JsonNode json = MAPPER.valueToTree(config);
334+
335+
JsonNode providerNode = json.get("provider");
336+
assertNotNull(providerNode);
337+
assertEquals("azure-openai", providerNode.get("type").asText());
338+
assertEquals("2024-02-01", providerNode.get("azure").get("apiVersion").asText());
339+
}
340+
341+
@Test
342+
void testSessionConfigWithoutProviderOmitsField() throws Exception {
343+
var config = new SessionConfig().setModel("gpt-4");
344+
345+
JsonNode json = MAPPER.valueToTree(config);
346+
347+
assertTrue(json.path("provider").isMissingNode(), "provider field should be omitted when null");
348+
}
349+
350+
// =========================================================================
351+
// Integration with ResumeSessionConfig
352+
// =========================================================================
353+
354+
@Test
355+
void testResumeSessionConfigWithProvider() throws Exception {
356+
var config = new ResumeSessionConfig().setStreaming(true).setProvider(new ProviderConfig().setType("openai")
357+
.setBaseUrl("https://api.openai.com/v1").setBearerToken("my-bearer-token"));
358+
359+
assertNotNull(config.getProvider());
360+
assertEquals("openai", config.getProvider().getType());
361+
assertEquals("https://api.openai.com/v1", config.getProvider().getBaseUrl());
362+
assertEquals("my-bearer-token", config.getProvider().getBearerToken());
363+
}
364+
365+
@Test
366+
void testResumeSessionConfigProviderSerialization() throws Exception {
367+
var config = new ResumeSessionConfig().setProvider(
368+
new ProviderConfig().setType("azure-openai").setBaseUrl("https://my-resource.openai.azure.com")
369+
.setApiKey("key").setAzure(new AzureOptions().setApiVersion("2024-02-01")));
370+
371+
JsonNode json = MAPPER.valueToTree(config);
372+
373+
JsonNode providerNode = json.get("provider");
374+
assertNotNull(providerNode);
375+
assertEquals("azure-openai", providerNode.get("type").asText());
376+
assertEquals("https://my-resource.openai.azure.com", providerNode.get("baseUrl").asText());
377+
assertEquals("key", providerNode.get("apiKey").asText());
378+
assertEquals("2024-02-01", providerNode.get("azure").get("apiVersion").asText());
379+
}
380+
381+
@Test
382+
void testResumeSessionConfigWithoutProviderOmitsField() throws Exception {
383+
var config = new ResumeSessionConfig().setStreaming(true);
384+
385+
JsonNode json = MAPPER.valueToTree(config);
386+
387+
assertTrue(json.path("provider").isMissingNode(), "provider field should be omitted when null");
388+
}
389+
}

0 commit comments

Comments
 (0)