@@ -91,7 +91,7 @@ void testBuilderWithAllFields() {
91
91
"streamOptions" , "seed" , "stop" , "temperature" , "topP" , "tools" , "toolChoice" , "user" ,
92
92
"parallelToolCalls" , "store" , "metadata" , "reasoningEffort" , "internalToolExecutionEnabled" ,
93
93
"httpHeaders" , "toolContext" )
94
- .containsExactly ("test-model" , 0.5 , logitBias , true , 5 , 100 , 50 , 2 , outputModalities , outputAudio , 0.8 ,
94
+ .containsExactly ("test-model" , 0.5 , logitBias , true , 5 , null , 50 , 2 , outputModalities , outputAudio , 0.8 ,
95
95
responseFormat , streamOptions , 12345 , stopSequences , 0.7 , 0.9 , tools , toolChoice , "test-user" , true ,
96
96
false , metadata , "medium" , false , Map .of ("header1" , "value1" ), toolContext );
97
97
@@ -120,8 +120,8 @@ void testCopy() {
120
120
.logitBias (logitBias )
121
121
.logprobs (true )
122
122
.topLogprobs (5 )
123
- .maxTokens ( 100 )
124
- . maxCompletionTokens ( 50 )
123
+ .maxCompletionTokens ( 50 ) // Only set maxCompletionTokens to avoid validation
124
+ // conflict
125
125
.N (2 )
126
126
.outputModalities (outputModalities )
127
127
.outputAudio (outputAudio )
@@ -280,4 +280,251 @@ void testFromOptions_webSearchOptions() {
280
280
assertThat (target .getWebSearchOptions ().userLocation ().approximate ().timezone ()).isEqualTo ("UTC+8" );
281
281
}
282
282
283
+ @ Test
284
+ void testEqualsAndHashCode () {
285
+ OpenAiChatOptions options1 = OpenAiChatOptions .builder ()
286
+ .model ("test-model" )
287
+ .temperature (0.7 )
288
+ .maxTokens (100 )
289
+ .build ();
290
+
291
+ OpenAiChatOptions options2 = OpenAiChatOptions .builder ()
292
+ .model ("test-model" )
293
+ .temperature (0.7 )
294
+ .maxTokens (100 )
295
+ .build ();
296
+
297
+ OpenAiChatOptions options3 = OpenAiChatOptions .builder ()
298
+ .model ("different-model" )
299
+ .temperature (0.7 )
300
+ .maxTokens (100 )
301
+ .build ();
302
+
303
+ // Test equals
304
+ assertThat (options1 ).isEqualTo (options2 );
305
+ assertThat (options1 ).isNotEqualTo (options3 );
306
+ assertThat (options1 ).isNotEqualTo (null );
307
+ assertThat (options1 ).isEqualTo (options1 );
308
+
309
+ // Test hashCode
310
+ assertThat (options1 .hashCode ()).isEqualTo (options2 .hashCode ());
311
+ assertThat (options1 .hashCode ()).isNotEqualTo (options3 .hashCode ());
312
+ }
313
+
314
+ @ Test
315
+ void testBuilderWithNullValues () {
316
+ OpenAiChatOptions options = OpenAiChatOptions .builder ()
317
+ .temperature (null )
318
+ .logitBias (null )
319
+ .stop (null )
320
+ .tools (null )
321
+ .metadata (null )
322
+ .build ();
323
+
324
+ assertThat (options .getModel ()).isNull ();
325
+ assertThat (options .getTemperature ()).isNull ();
326
+ assertThat (options .getLogitBias ()).isNull ();
327
+ assertThat (options .getStop ()).isNull ();
328
+ assertThat (options .getTools ()).isNull ();
329
+ assertThat (options .getMetadata ()).isNull ();
330
+ }
331
+
332
+ @ Test
333
+ void testBuilderChaining () {
334
+ OpenAiChatOptions .Builder builder = OpenAiChatOptions .builder ();
335
+
336
+ OpenAiChatOptions .Builder result = builder .model ("test-model" ).temperature (0.7 ).maxTokens (100 );
337
+
338
+ assertThat (result ).isSameAs (builder );
339
+
340
+ OpenAiChatOptions options = result .build ();
341
+ assertThat (options .getModel ()).isEqualTo ("test-model" );
342
+ assertThat (options .getTemperature ()).isEqualTo (0.7 );
343
+ assertThat (options .getMaxTokens ()).isEqualTo (100 );
344
+ }
345
+
346
+ @ Test
347
+ void testNullAndEmptyCollections () {
348
+ OpenAiChatOptions options = new OpenAiChatOptions ();
349
+
350
+ // Test setting null collections
351
+ options .setLogitBias (null );
352
+ options .setStop (null );
353
+ options .setTools (null );
354
+ options .setMetadata (null );
355
+ options .setOutputModalities (null );
356
+
357
+ assertThat (options .getLogitBias ()).isNull ();
358
+ assertThat (options .getStop ()).isNull ();
359
+ assertThat (options .getTools ()).isNull ();
360
+ assertThat (options .getMetadata ()).isNull ();
361
+ assertThat (options .getOutputModalities ()).isNull ();
362
+
363
+ // Test setting empty collections
364
+ options .setLogitBias (new HashMap <>());
365
+ options .setStop (new ArrayList <>());
366
+ options .setTools (new ArrayList <>());
367
+ options .setMetadata (new HashMap <>());
368
+ options .setOutputModalities (new ArrayList <>());
369
+
370
+ assertThat (options .getLogitBias ()).isEmpty ();
371
+ assertThat (options .getStop ()).isEmpty ();
372
+ assertThat (options .getTools ()).isEmpty ();
373
+ assertThat (options .getMetadata ()).isEmpty ();
374
+ assertThat (options .getOutputModalities ()).isEmpty ();
375
+ }
376
+
377
+ @ Test
378
+ void testStreamUsageStreamOptionsInteraction () {
379
+ OpenAiChatOptions options = new OpenAiChatOptions ();
380
+
381
+ // Initially false
382
+ assertThat (options .getStreamUsage ()).isFalse ();
383
+ assertThat (options .getStreamOptions ()).isNull ();
384
+
385
+ // Setting streamUsage to true should set streamOptions
386
+ options .setStreamUsage (true );
387
+ assertThat (options .getStreamUsage ()).isTrue ();
388
+ assertThat (options .getStreamOptions ()).isEqualTo (StreamOptions .INCLUDE_USAGE );
389
+
390
+ // Setting streamUsage to false should clear streamOptions
391
+ options .setStreamUsage (false );
392
+ assertThat (options .getStreamUsage ()).isFalse ();
393
+ assertThat (options .getStreamOptions ()).isNull ();
394
+
395
+ // Setting streamOptions directly should update streamUsage
396
+ options .setStreamOptions (StreamOptions .INCLUDE_USAGE );
397
+ assertThat (options .getStreamUsage ()).isTrue ();
398
+ assertThat (options .getStreamOptions ()).isEqualTo (StreamOptions .INCLUDE_USAGE );
399
+
400
+ // Setting streamOptions to null should set streamUsage to false
401
+ options .setStreamOptions (null );
402
+ assertThat (options .getStreamUsage ()).isFalse ();
403
+ assertThat (options .getStreamOptions ()).isNull ();
404
+ }
405
+
406
+ @ Test
407
+ void testStopSequencesAlias () {
408
+ OpenAiChatOptions options = new OpenAiChatOptions ();
409
+ List <String > stopSequences = List .of ("stop1" , "stop2" );
410
+
411
+ // Setting stopSequences should also set stop
412
+ options .setStopSequences (stopSequences );
413
+ assertThat (options .getStopSequences ()).isEqualTo (stopSequences );
414
+ assertThat (options .getStop ()).isEqualTo (stopSequences );
415
+
416
+ // Setting stop should also update stopSequences
417
+ List <String > newStop = List .of ("stop3" , "stop4" );
418
+ options .setStop (newStop );
419
+ assertThat (options .getStop ()).isEqualTo (newStop );
420
+ assertThat (options .getStopSequences ()).isEqualTo (newStop );
421
+ }
422
+
423
+ @ Test
424
+ void testFromOptionsWithWebSearchOptionsNull () {
425
+ OpenAiChatOptions source = OpenAiChatOptions .builder ()
426
+ .model ("test-model" )
427
+ .temperature (0.7 )
428
+ .webSearchOptions (null )
429
+ .build ();
430
+
431
+ OpenAiChatOptions result = OpenAiChatOptions .fromOptions (source );
432
+ assertThat (result .getModel ()).isEqualTo ("test-model" );
433
+ assertThat (result .getTemperature ()).isEqualTo (0.7 );
434
+ assertThat (result .getWebSearchOptions ()).isNull ();
435
+ }
436
+
437
+ @ Test
438
+ void testCopyChangeIndependence () {
439
+ OpenAiChatOptions original = OpenAiChatOptions .builder ().model ("original-model" ).temperature (0.5 ).build ();
440
+
441
+ OpenAiChatOptions copied = original .copy ();
442
+
443
+ // Modify original
444
+ original .setModel ("modified-model" );
445
+ original .setTemperature (0.9 );
446
+
447
+ // Verify copy is unchanged
448
+ assertThat (copied .getModel ()).isEqualTo ("original-model" );
449
+ assertThat (copied .getTemperature ()).isEqualTo (0.5 );
450
+ }
451
+
452
+ @ Test
453
+ void testMaxTokensMutualExclusivityValidation () {
454
+ // Test that setting maxTokens clears maxCompletionTokens
455
+ OpenAiChatOptions options = OpenAiChatOptions .builder ()
456
+ .maxCompletionTokens (100 )
457
+ .maxTokens (50 ) // This should clear maxCompletionTokens
458
+ .build ();
459
+
460
+ assertThat (options .getMaxTokens ()).isEqualTo (50 );
461
+ assertThat (options .getMaxCompletionTokens ()).isNull ();
462
+ }
463
+
464
+ @ Test
465
+ void testMaxCompletionTokensMutualExclusivityValidation () {
466
+ // Test that setting maxCompletionTokens clears maxTokens
467
+ OpenAiChatOptions options = OpenAiChatOptions .builder ()
468
+ .maxTokens (50 )
469
+ .maxCompletionTokens (100 ) // This should clear maxTokens
470
+ .build ();
471
+
472
+ assertThat (options .getMaxTokens ()).isNull ();
473
+ assertThat (options .getMaxCompletionTokens ()).isEqualTo (100 );
474
+ }
475
+
476
+ @ Test
477
+ void testMaxTokensWithNullDoesNotClearMaxCompletionTokens () {
478
+ // Test that setting maxTokens to null doesn't trigger validation
479
+ OpenAiChatOptions options = OpenAiChatOptions .builder ()
480
+ .maxCompletionTokens (100 )
481
+ .maxTokens (null ) // This should not clear maxCompletionTokens
482
+ .build ();
483
+
484
+ assertThat (options .getMaxTokens ()).isNull ();
485
+ assertThat (options .getMaxCompletionTokens ()).isEqualTo (100 );
486
+ }
487
+
488
+ @ Test
489
+ void testMaxCompletionTokensWithNullDoesNotClearMaxTokens () {
490
+ // Test that setting maxCompletionTokens to null doesn't trigger validation
491
+ OpenAiChatOptions options = OpenAiChatOptions .builder ()
492
+ .maxTokens (50 )
493
+ .maxCompletionTokens (null ) // This should not clear maxTokens
494
+ .build ();
495
+
496
+ assertThat (options .getMaxTokens ()).isEqualTo (50 );
497
+ assertThat (options .getMaxCompletionTokens ()).isNull ();
498
+ }
499
+
500
+ @ Test
501
+ void testBuilderCanSetOnlyMaxTokens () {
502
+ // Test that we can set only maxTokens without issues
503
+ OpenAiChatOptions options = OpenAiChatOptions .builder ().maxTokens (100 ).build ();
504
+
505
+ assertThat (options .getMaxTokens ()).isEqualTo (100 );
506
+ assertThat (options .getMaxCompletionTokens ()).isNull ();
507
+ }
508
+
509
+ @ Test
510
+ void testBuilderCanSetOnlyMaxCompletionTokens () {
511
+ // Test that we can set only maxCompletionTokens without issues
512
+ OpenAiChatOptions options = OpenAiChatOptions .builder ().maxCompletionTokens (150 ).build ();
513
+
514
+ assertThat (options .getMaxTokens ()).isNull ();
515
+ assertThat (options .getMaxCompletionTokens ()).isEqualTo (150 );
516
+ }
517
+
518
+ @ Test
519
+ void testSettersMutualExclusivityNotEnforced () {
520
+ // Test that direct setters do NOT enforce mutual exclusivity (only builder does)
521
+ OpenAiChatOptions options = new OpenAiChatOptions ();
522
+ options .setMaxTokens (50 );
523
+ options .setMaxCompletionTokens (100 );
524
+
525
+ // Both should be set when using setters directly
526
+ assertThat (options .getMaxTokens ()).isEqualTo (50 );
527
+ assertThat (options .getMaxCompletionTokens ()).isEqualTo (100 );
528
+ }
529
+
283
530
}
0 commit comments