@@ -4,23 +4,24 @@ package com.browserbase.api.models.sessions
44
55import com.browserbase.api.core.JsonValue
66import com.browserbase.api.core.jsonMapper
7+ import com.browserbase.api.errors.StagehandInvalidDataException
78import com.fasterxml.jackson.module.kotlin.jacksonTypeRef
89import org.assertj.core.api.Assertions.assertThat
910import org.junit.jupiter.api.Test
11+ import org.junit.jupiter.api.assertThrows
12+ import org.junit.jupiter.params.ParameterizedTest
13+ import org.junit.jupiter.params.provider.EnumSource
1014
1115internal class ModelConfigTest {
1216
1317 @Test
14- fun create () {
15- val modelConfig =
16- ModelConfig .builder()
17- .modelName(" openai/gpt-5.4-mini" )
18- .apiKey(" sk-some-openai-api-key" )
19- .baseUrl(" https://api.openai.com/v1" )
20- .googleAuthOptions(
21- ModelConfig .GoogleAuthOptions .builder()
18+ fun ofVertexModelConfigObject () {
19+ val vertexModelConfigObject =
20+ ModelConfig .VertexModelConfigObject .builder()
21+ .auth(
22+ ModelConfig .VertexModelConfigObject .Auth .builder()
2223 .credentials(
23- ModelConfig .GoogleAuthOptions .Credentials .builder()
24+ ModelConfig .VertexModelConfigObject . Auth .Credentials .builder()
2425 .clientEmail(" client_email" )
2526 .privateKey(" private_key" )
2627 .authProviderX509CertUrl(" https://example.com" )
@@ -31,7 +32,8 @@ internal class ModelConfigTest {
3132 .projectId(" project_id" )
3233 .tokenUri(" https://example.com" )
3334 .type(
34- ModelConfig .GoogleAuthOptions .Credentials .Type .SERVICE_ACCOUNT
35+ ModelConfig .VertexModelConfigObject .Auth .Credentials .Type
36+ .SERVICE_ACCOUNT
3537 )
3638 .universeDomain(" universe_domain" )
3739 .build()
@@ -41,95 +43,148 @@ internal class ModelConfigTest {
4143 .universeDomain(" universeDomain" )
4244 .build()
4345 )
46+ .modelName(" openai/gpt-5.4-mini" )
47+ .providerOptions(
48+ ModelConfig .VertexModelConfigObject .ProviderOptions .builder()
49+ .vertex(
50+ ModelConfig .VertexModelConfigObject .ProviderOptions .Vertex .builder()
51+ .location(" us-central1" )
52+ .project(" my-gcp-project" )
53+ .baseUrl(" https://example.com" )
54+ .headers(
55+ ModelConfig .VertexModelConfigObject .ProviderOptions .Vertex
56+ .Headers
57+ .builder()
58+ .putAdditionalProperty(" foo" , JsonValue .from(" string" ))
59+ .build()
60+ )
61+ .build()
62+ )
63+ .build()
64+ )
65+ .apiKey(" sk-some-openai-api-key" )
66+ .baseUrl(" https://api.openai.com/v1" )
4467 .headers(
45- ModelConfig .Headers .builder()
68+ ModelConfig .VertexModelConfigObject . Headers .builder()
4669 .putAdditionalProperty(" foo" , JsonValue .from(" string" ))
4770 .build()
4871 )
49- .location(" us-central1" )
50- .project(" my-gcp-project" )
51- .provider(ModelConfig .Provider .OPENAI )
5272 .build()
5373
54- assertThat(modelConfig.modelName()).isEqualTo(" openai/gpt-5.4-mini" )
55- assertThat(modelConfig.apiKey()).contains(" sk-some-openai-api-key" )
56- assertThat(modelConfig.baseUrl()).contains(" https://api.openai.com/v1" )
57- assertThat(modelConfig.googleAuthOptions())
58- .contains(
59- ModelConfig .GoogleAuthOptions .builder()
60- .credentials(
61- ModelConfig .GoogleAuthOptions .Credentials .builder()
62- .clientEmail(" client_email" )
63- .privateKey(" private_key" )
64- .authProviderX509CertUrl(" https://example.com" )
65- .authUri(" https://example.com" )
66- .clientId(" client_id" )
67- .clientX509CertUrl(" https://example.com" )
68- .privateKeyId(" private_key_id" )
69- .projectId(" project_id" )
70- .tokenUri(" https://example.com" )
71- .type(ModelConfig .GoogleAuthOptions .Credentials .Type .SERVICE_ACCOUNT )
72- .universeDomain(" universe_domain" )
74+ val modelConfig = ModelConfig .ofVertexModelConfigObject(vertexModelConfigObject)
75+
76+ assertThat(modelConfig.vertexModelConfigObject()).contains(vertexModelConfigObject)
77+ assertThat(modelConfig.genericModelConfigObject()).isEmpty
78+ }
79+
80+ @Test
81+ fun ofVertexModelConfigObjectRoundtrip () {
82+ val jsonMapper = jsonMapper()
83+ val modelConfig =
84+ ModelConfig .ofVertexModelConfigObject(
85+ ModelConfig .VertexModelConfigObject .builder()
86+ .auth(
87+ ModelConfig .VertexModelConfigObject .Auth .builder()
88+ .credentials(
89+ ModelConfig .VertexModelConfigObject .Auth .Credentials .builder()
90+ .clientEmail(" client_email" )
91+ .privateKey(" private_key" )
92+ .authProviderX509CertUrl(" https://example.com" )
93+ .authUri(" https://example.com" )
94+ .clientId(" client_id" )
95+ .clientX509CertUrl(" https://example.com" )
96+ .privateKeyId(" private_key_id" )
97+ .projectId(" project_id" )
98+ .tokenUri(" https://example.com" )
99+ .type(
100+ ModelConfig .VertexModelConfigObject .Auth .Credentials .Type
101+ .SERVICE_ACCOUNT
102+ )
103+ .universeDomain(" universe_domain" )
104+ .build()
105+ )
106+ .projectId(" projectId" )
107+ .scopes(" string" )
108+ .universeDomain(" universeDomain" )
109+ .build()
110+ )
111+ .modelName(" openai/gpt-5.4-mini" )
112+ .providerOptions(
113+ ModelConfig .VertexModelConfigObject .ProviderOptions .builder()
114+ .vertex(
115+ ModelConfig .VertexModelConfigObject .ProviderOptions .Vertex .builder()
116+ .location(" us-central1" )
117+ .project(" my-gcp-project" )
118+ .baseUrl(" https://example.com" )
119+ .headers(
120+ ModelConfig .VertexModelConfigObject .ProviderOptions .Vertex
121+ .Headers
122+ .builder()
123+ .putAdditionalProperty(" foo" , JsonValue .from(" string" ))
124+ .build()
125+ )
126+ .build()
127+ )
128+ .build()
129+ )
130+ .apiKey(" sk-some-openai-api-key" )
131+ .baseUrl(" https://api.openai.com/v1" )
132+ .headers(
133+ ModelConfig .VertexModelConfigObject .Headers .builder()
134+ .putAdditionalProperty(" foo" , JsonValue .from(" string" ))
73135 .build()
74136 )
75- .projectId(" projectId" )
76- .scopes(" string" )
77- .universeDomain(" universeDomain" )
78137 .build()
79138 )
80- assertThat(modelConfig.headers())
81- .contains(
82- ModelConfig . Headers .builder()
83- .putAdditionalProperty( " foo " , JsonValue .from( " string " ))
84- .build()
139+
140+ val roundtrippedModelConfig =
141+ jsonMapper.readValue(
142+ jsonMapper.writeValueAsString(modelConfig),
143+ jacksonTypeRef< ModelConfig >(),
85144 )
86- assertThat(modelConfig.location()).contains(" us-central1" )
87- assertThat(modelConfig.project()).contains(" my-gcp-project" )
88- assertThat(modelConfig.provider()).contains(ModelConfig .Provider .OPENAI )
145+
146+ assertThat(roundtrippedModelConfig).isEqualTo(modelConfig)
89147 }
90148
91149 @Test
92- fun roundtrip () {
93- val jsonMapper = jsonMapper()
94- val modelConfig =
95- ModelConfig .builder()
150+ fun ofGenericModelConfigObject () {
151+ val genericModelConfigObject =
152+ ModelConfig .GenericModelConfigObject .builder()
96153 .modelName(" openai/gpt-5.4-mini" )
97154 .apiKey(" sk-some-openai-api-key" )
98155 .baseUrl(" https://api.openai.com/v1" )
99- .googleAuthOptions(
100- ModelConfig .GoogleAuthOptions .builder()
101- .credentials(
102- ModelConfig .GoogleAuthOptions .Credentials .builder()
103- .clientEmail(" client_email" )
104- .privateKey(" private_key" )
105- .authProviderX509CertUrl(" https://example.com" )
106- .authUri(" https://example.com" )
107- .clientId(" client_id" )
108- .clientX509CertUrl(" https://example.com" )
109- .privateKeyId(" private_key_id" )
110- .projectId(" project_id" )
111- .tokenUri(" https://example.com" )
112- .type(
113- ModelConfig .GoogleAuthOptions .Credentials .Type .SERVICE_ACCOUNT
114- )
115- .universeDomain(" universe_domain" )
116- .build()
117- )
118- .projectId(" projectId" )
119- .scopes(" string" )
120- .universeDomain(" universeDomain" )
121- .build()
122- )
123156 .headers(
124- ModelConfig .Headers .builder()
157+ ModelConfig .GenericModelConfigObject . Headers .builder()
125158 .putAdditionalProperty(" foo" , JsonValue .from(" string" ))
126159 .build()
127160 )
128- .location(" us-central1" )
129- .project(" my-gcp-project" )
130- .provider(ModelConfig .Provider .OPENAI )
161+ .provider(ModelConfig .GenericModelConfigObject .Provider .OPENAI )
131162 .build()
132163
164+ val modelConfig = ModelConfig .ofGenericModelConfigObject(genericModelConfigObject)
165+
166+ assertThat(modelConfig.vertexModelConfigObject()).isEmpty
167+ assertThat(modelConfig.genericModelConfigObject()).contains(genericModelConfigObject)
168+ }
169+
170+ @Test
171+ fun ofGenericModelConfigObjectRoundtrip () {
172+ val jsonMapper = jsonMapper()
173+ val modelConfig =
174+ ModelConfig .ofGenericModelConfigObject(
175+ ModelConfig .GenericModelConfigObject .builder()
176+ .modelName(" openai/gpt-5.4-mini" )
177+ .apiKey(" sk-some-openai-api-key" )
178+ .baseUrl(" https://api.openai.com/v1" )
179+ .headers(
180+ ModelConfig .GenericModelConfigObject .Headers .builder()
181+ .putAdditionalProperty(" foo" , JsonValue .from(" string" ))
182+ .build()
183+ )
184+ .provider(ModelConfig .GenericModelConfigObject .Provider .OPENAI )
185+ .build()
186+ )
187+
133188 val roundtrippedModelConfig =
134189 jsonMapper.readValue(
135190 jsonMapper.writeValueAsString(modelConfig),
@@ -138,4 +193,21 @@ internal class ModelConfigTest {
138193
139194 assertThat(roundtrippedModelConfig).isEqualTo(modelConfig)
140195 }
196+
197+ enum class IncompatibleJsonShapeTestCase (val value : JsonValue ) {
198+ BOOLEAN (JsonValue .from(false )),
199+ STRING (JsonValue .from(" invalid" )),
200+ INTEGER (JsonValue .from(- 1 )),
201+ FLOAT (JsonValue .from(3.14 )),
202+ ARRAY (JsonValue .from(listOf (" invalid" , " array" ))),
203+ }
204+
205+ @ParameterizedTest
206+ @EnumSource
207+ fun incompatibleJsonShapeDeserializesToUnknown (testCase : IncompatibleJsonShapeTestCase ) {
208+ val modelConfig = jsonMapper().convertValue(testCase.value, jacksonTypeRef<ModelConfig >())
209+
210+ val e = assertThrows<StagehandInvalidDataException > { modelConfig.validate() }
211+ assertThat(e).hasMessageStartingWith(" Unknown " )
212+ }
141213}
0 commit comments