@@ -1006,6 +1006,99 @@ defmodule CodexPooler.Gateway.Payloads.RequestOptionsTest do
10061006 assert options . file_bridge . pool_upstream_assignment_id == "assignment-id"
10071007 end
10081008
1009+ test "optional normalizers ignore invalid updates instead of clearing existing values" do
1010+ options =
1011+ RequestOptions . build (
1012+ % {
1013+ session_header_source: "session-id" ,
1014+ forwarded_headers: [ { "x-codex-client" , "fixture" } ] ,
1015+ finalize_retry_timeout_ms: 500 ,
1016+ payload_compression: % { "attempted" => true , "status" => "compressed" } ,
1017+ websocket_owner_forwarding_enabled?: true ,
1018+ websocket_owner_downstream: % { pid: self ( ) } ,
1019+ websocket_owner_downstream_epoch: 3
1020+ } ,
1021+ "/backend-api/codex/responses" ,
1022+ % { "model" => "example-model" }
1023+ )
1024+
1025+ updated =
1026+ options
1027+ |> RequestOptions . put_continuity (
1028+ session_header_source: "x-unsafe-header" ,
1029+ reconnect_window_seconds: - 1
1030+ )
1031+ |> RequestOptions . put_transport (
1032+ forwarded_metadata_headers: [ { "x-codex-client" , :invalid } ] ,
1033+ websocket_owner_downstream_epoch: "stale"
1034+ )
1035+ |> RequestOptions . put_file_bridge (
1036+ forwarded_headers: [ { "x-codex-client" , :invalid } ] ,
1037+ finalize_retry_timeout_ms: - 1
1038+ )
1039+ |> RequestOptions . put_runtime_context (
1040+ payload_compression: % { "enabled" => true , "attempted" => false , "status" => "disabled" }
1041+ )
1042+
1043+ assert updated . continuity . session_header_source == "session-id"
1044+ assert updated . continuity . reconnect_window_seconds == nil
1045+ assert updated . transport . forwarded_metadata_headers == [ { "x-codex-client" , "fixture" } ]
1046+ assert updated . transport . websocket_owner . downstream_epoch == 3
1047+ assert updated . file_bridge . forwarded_headers == [ { "x-codex-client" , "fixture" } ]
1048+ assert updated . file_bridge . finalize_retry_timeout_ms == 500
1049+
1050+ assert updated . runtime . payload_compression == % {
1051+ "attempted" => true ,
1052+ "status" => "compressed"
1053+ }
1054+ end
1055+
1056+ test "optional normalizers still accept explicit valid replacements" do
1057+ options =
1058+ RequestOptions . build (
1059+ % {
1060+ session_header_source: "session-id" ,
1061+ forwarded_headers: [ { "x-codex-client" , "fixture" } ] ,
1062+ finalize_retry_timeout_ms: 500 ,
1063+ payload_compression: % { "attempted" => true , "status" => "compressed" } ,
1064+ websocket_owner_forwarding_enabled?: true ,
1065+ websocket_owner_downstream_epoch: 3
1066+ } ,
1067+ "/backend-api/codex/responses" ,
1068+ % { "model" => "example-model" }
1069+ )
1070+
1071+ updated =
1072+ options
1073+ |> RequestOptions . put_continuity ( session_header_source: "x-session-id" )
1074+ |> RequestOptions . put_transport (
1075+ forwarded_metadata_headers: [ { "x-openai-client-user-agent" , "synthetic" } ] ,
1076+ websocket_owner_downstream_epoch: 4
1077+ )
1078+ |> RequestOptions . put_file_bridge (
1079+ forwarded_headers: [ { "user-agent" , "codex_cli_rs/0.0.0" } ] ,
1080+ finalize_retry_timeout_ms: 0
1081+ )
1082+ |> RequestOptions . put_runtime_context (
1083+ payload_compression: % { "attempted" => true , "status" => "no_change" }
1084+ )
1085+
1086+ assert updated . continuity . session_header_source == "x-session-id"
1087+
1088+ assert updated . transport . forwarded_metadata_headers == [
1089+ { "x-openai-client-user-agent" , "synthetic" }
1090+ ]
1091+
1092+ assert updated . transport . websocket_owner . downstream_epoch == 4
1093+ assert updated . file_bridge . forwarded_headers == [ { "user-agent" , "codex_cli_rs/0.0.0" } ]
1094+ assert updated . file_bridge . finalize_retry_timeout_ms == 0
1095+
1096+ assert updated . runtime . payload_compression == % {
1097+ "attempted" => true ,
1098+ "status" => "no_change"
1099+ }
1100+ end
1101+
10091102 test "reject unknown section fields" do
10101103 options =
10111104 RequestOptions . build ( % { } , "/backend-api/codex/responses" , % { "model" => "example-model" } )
0 commit comments