override_test.go 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950
  1. package common
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "reflect"
  6. "testing"
  7. "github.com/QuantumNous/new-api/types"
  8. "github.com/QuantumNous/new-api/dto"
  9. "github.com/QuantumNous/new-api/setting/model_setting"
  10. "github.com/samber/lo"
  11. )
  12. func TestApplyParamOverrideTrimPrefix(t *testing.T) {
  13. // trim_prefix example:
  14. // {"operations":[{"path":"model","mode":"trim_prefix","value":"openai/"}]}
  15. input := []byte(`{"model":"openai/gpt-4","temperature":0.7}`)
  16. override := map[string]interface{}{
  17. "operations": []interface{}{
  18. map[string]interface{}{
  19. "path": "model",
  20. "mode": "trim_prefix",
  21. "value": "openai/",
  22. },
  23. },
  24. }
  25. out, err := ApplyParamOverride(input, override, nil)
  26. if err != nil {
  27. t.Fatalf("ApplyParamOverride returned error: %v", err)
  28. }
  29. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
  30. }
  31. func TestApplyParamOverrideTrimSuffix(t *testing.T) {
  32. // trim_suffix example:
  33. // {"operations":[{"path":"model","mode":"trim_suffix","value":"-latest"}]}
  34. input := []byte(`{"model":"gpt-4-latest","temperature":0.7}`)
  35. override := map[string]interface{}{
  36. "operations": []interface{}{
  37. map[string]interface{}{
  38. "path": "model",
  39. "mode": "trim_suffix",
  40. "value": "-latest",
  41. },
  42. },
  43. }
  44. out, err := ApplyParamOverride(input, override, nil)
  45. if err != nil {
  46. t.Fatalf("ApplyParamOverride returned error: %v", err)
  47. }
  48. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
  49. }
  50. func TestApplyParamOverrideTrimNoop(t *testing.T) {
  51. // trim_prefix no-op example:
  52. // {"operations":[{"path":"model","mode":"trim_prefix","value":"openai/"}]}
  53. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  54. override := map[string]interface{}{
  55. "operations": []interface{}{
  56. map[string]interface{}{
  57. "path": "model",
  58. "mode": "trim_prefix",
  59. "value": "openai/",
  60. },
  61. },
  62. }
  63. out, err := ApplyParamOverride(input, override, nil)
  64. if err != nil {
  65. t.Fatalf("ApplyParamOverride returned error: %v", err)
  66. }
  67. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
  68. }
  69. func TestApplyParamOverrideMixedLegacyAndOperations(t *testing.T) {
  70. input := []byte(`{"model":"openai/gpt-4","temperature":0.7}`)
  71. override := map[string]interface{}{
  72. "temperature": 0.2,
  73. "top_p": 0.95,
  74. "operations": []interface{}{
  75. map[string]interface{}{
  76. "path": "model",
  77. "mode": "trim_prefix",
  78. "value": "openai/",
  79. },
  80. },
  81. }
  82. out, err := ApplyParamOverride(input, override, nil)
  83. if err != nil {
  84. t.Fatalf("ApplyParamOverride returned error: %v", err)
  85. }
  86. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.2,"top_p":0.95}`, string(out))
  87. }
  88. func TestApplyParamOverrideMixedLegacyAndOperationsConflictPrefersOperations(t *testing.T) {
  89. input := []byte(`{"model":"openai/gpt-4","temperature":0.7}`)
  90. override := map[string]interface{}{
  91. "model": "legacy-model",
  92. "temperature": 0.2,
  93. "operations": []interface{}{
  94. map[string]interface{}{
  95. "path": "model",
  96. "mode": "set",
  97. "value": "op-model",
  98. },
  99. },
  100. }
  101. out, err := ApplyParamOverride(input, override, nil)
  102. if err != nil {
  103. t.Fatalf("ApplyParamOverride returned error: %v", err)
  104. }
  105. assertJSONEqual(t, `{"model":"op-model","temperature":0.2}`, string(out))
  106. }
  107. func TestApplyParamOverrideTrimRequiresValue(t *testing.T) {
  108. // trim_prefix requires value example:
  109. // {"operations":[{"path":"model","mode":"trim_prefix"}]}
  110. input := []byte(`{"model":"gpt-4"}`)
  111. override := map[string]interface{}{
  112. "operations": []interface{}{
  113. map[string]interface{}{
  114. "path": "model",
  115. "mode": "trim_prefix",
  116. },
  117. },
  118. }
  119. _, err := ApplyParamOverride(input, override, nil)
  120. if err == nil {
  121. t.Fatalf("expected error, got nil")
  122. }
  123. }
  124. func TestApplyParamOverrideReplace(t *testing.T) {
  125. // replace example:
  126. // {"operations":[{"path":"model","mode":"replace","from":"openai/","to":""}]}
  127. input := []byte(`{"model":"openai/gpt-4o-mini","temperature":0.7}`)
  128. override := map[string]interface{}{
  129. "operations": []interface{}{
  130. map[string]interface{}{
  131. "path": "model",
  132. "mode": "replace",
  133. "from": "openai/",
  134. "to": "",
  135. },
  136. },
  137. }
  138. out, err := ApplyParamOverride(input, override, nil)
  139. if err != nil {
  140. t.Fatalf("ApplyParamOverride returned error: %v", err)
  141. }
  142. assertJSONEqual(t, `{"model":"gpt-4o-mini","temperature":0.7}`, string(out))
  143. }
  144. func TestApplyParamOverrideRegexReplace(t *testing.T) {
  145. // regex_replace example:
  146. // {"operations":[{"path":"model","mode":"regex_replace","from":"^gpt-","to":"openai/gpt-"}]}
  147. input := []byte(`{"model":"gpt-4o-mini","temperature":0.7}`)
  148. override := map[string]interface{}{
  149. "operations": []interface{}{
  150. map[string]interface{}{
  151. "path": "model",
  152. "mode": "regex_replace",
  153. "from": "^gpt-",
  154. "to": "openai/gpt-",
  155. },
  156. },
  157. }
  158. out, err := ApplyParamOverride(input, override, nil)
  159. if err != nil {
  160. t.Fatalf("ApplyParamOverride returned error: %v", err)
  161. }
  162. assertJSONEqual(t, `{"model":"openai/gpt-4o-mini","temperature":0.7}`, string(out))
  163. }
  164. func TestApplyParamOverrideReplaceRequiresFrom(t *testing.T) {
  165. // replace requires from example:
  166. // {"operations":[{"path":"model","mode":"replace"}]}
  167. input := []byte(`{"model":"gpt-4"}`)
  168. override := map[string]interface{}{
  169. "operations": []interface{}{
  170. map[string]interface{}{
  171. "path": "model",
  172. "mode": "replace",
  173. },
  174. },
  175. }
  176. _, err := ApplyParamOverride(input, override, nil)
  177. if err == nil {
  178. t.Fatalf("expected error, got nil")
  179. }
  180. }
  181. func TestApplyParamOverrideRegexReplaceRequiresPattern(t *testing.T) {
  182. // regex_replace requires from(pattern) example:
  183. // {"operations":[{"path":"model","mode":"regex_replace"}]}
  184. input := []byte(`{"model":"gpt-4"}`)
  185. override := map[string]interface{}{
  186. "operations": []interface{}{
  187. map[string]interface{}{
  188. "path": "model",
  189. "mode": "regex_replace",
  190. },
  191. },
  192. }
  193. _, err := ApplyParamOverride(input, override, nil)
  194. if err == nil {
  195. t.Fatalf("expected error, got nil")
  196. }
  197. }
  198. func TestApplyParamOverrideDelete(t *testing.T) {
  199. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  200. override := map[string]interface{}{
  201. "operations": []interface{}{
  202. map[string]interface{}{
  203. "path": "temperature",
  204. "mode": "delete",
  205. },
  206. },
  207. }
  208. out, err := ApplyParamOverride(input, override, nil)
  209. if err != nil {
  210. t.Fatalf("ApplyParamOverride returned error: %v", err)
  211. }
  212. var got map[string]interface{}
  213. if err := json.Unmarshal(out, &got); err != nil {
  214. t.Fatalf("failed to unmarshal output JSON: %v", err)
  215. }
  216. if _, exists := got["temperature"]; exists {
  217. t.Fatalf("expected temperature to be deleted")
  218. }
  219. }
  220. func TestApplyParamOverrideDeleteWildcardPath(t *testing.T) {
  221. input := []byte(`{"tools":[{"type":"bash","custom":{"input_examples":["a"],"other":1}},{"type":"code","custom":{"input_examples":["b"]}},{"type":"noop","custom":{"other":2}}]}`)
  222. override := map[string]interface{}{
  223. "operations": []interface{}{
  224. map[string]interface{}{
  225. "path": "tools.*.custom.input_examples",
  226. "mode": "delete",
  227. },
  228. },
  229. }
  230. out, err := ApplyParamOverride(input, override, nil)
  231. if err != nil {
  232. t.Fatalf("ApplyParamOverride returned error: %v", err)
  233. }
  234. assertJSONEqual(t, `{"tools":[{"type":"bash","custom":{"other":1}},{"type":"code","custom":{}},{"type":"noop","custom":{"other":2}}]}`, string(out))
  235. }
  236. func TestApplyParamOverrideSetWildcardPath(t *testing.T) {
  237. input := []byte(`{"tools":[{"custom":{"tag":"A"}},{"custom":{"tag":"B"}},{"custom":{"tag":"C"}}]}`)
  238. override := map[string]interface{}{
  239. "operations": []interface{}{
  240. map[string]interface{}{
  241. "path": "tools.*.custom.enabled",
  242. "mode": "set",
  243. "value": true,
  244. },
  245. },
  246. }
  247. out, err := ApplyParamOverride(input, override, nil)
  248. if err != nil {
  249. t.Fatalf("ApplyParamOverride returned error: %v", err)
  250. }
  251. var got struct {
  252. Tools []struct {
  253. Custom struct {
  254. Enabled bool `json:"enabled"`
  255. } `json:"custom"`
  256. } `json:"tools"`
  257. }
  258. if err := json.Unmarshal(out, &got); err != nil {
  259. t.Fatalf("failed to unmarshal output JSON: %v", err)
  260. }
  261. if !lo.EveryBy(got.Tools, func(item struct {
  262. Custom struct {
  263. Enabled bool `json:"enabled"`
  264. } `json:"custom"`
  265. }) bool {
  266. return item.Custom.Enabled
  267. }) {
  268. t.Fatalf("expected wildcard set to enable all tools, got: %s", string(out))
  269. }
  270. }
  271. func TestApplyParamOverrideTrimSpaceWildcardPath(t *testing.T) {
  272. input := []byte(`{"tools":[{"custom":{"name":" alpha "}},{"custom":{"name":" beta"}},{"custom":{"name":"gamma "}}]}`)
  273. override := map[string]interface{}{
  274. "operations": []interface{}{
  275. map[string]interface{}{
  276. "path": "tools.*.custom.name",
  277. "mode": "trim_space",
  278. },
  279. },
  280. }
  281. out, err := ApplyParamOverride(input, override, nil)
  282. if err != nil {
  283. t.Fatalf("ApplyParamOverride returned error: %v", err)
  284. }
  285. var got struct {
  286. Tools []struct {
  287. Custom struct {
  288. Name string `json:"name"`
  289. } `json:"custom"`
  290. } `json:"tools"`
  291. }
  292. if err := json.Unmarshal(out, &got); err != nil {
  293. t.Fatalf("failed to unmarshal output JSON: %v", err)
  294. }
  295. names := lo.Map(got.Tools, func(item struct {
  296. Custom struct {
  297. Name string `json:"name"`
  298. } `json:"custom"`
  299. }, _ int) string {
  300. return item.Custom.Name
  301. })
  302. if !reflect.DeepEqual(names, []string{"alpha", "beta", "gamma"}) {
  303. t.Fatalf("unexpected names after wildcard trim_space: %v", names)
  304. }
  305. }
  306. func TestApplyParamOverrideDeleteWildcardEqualsIndexedPaths(t *testing.T) {
  307. input := []byte(`{"tools":[{"custom":{"input_examples":["a"],"other":1}},{"custom":{"input_examples":["b"],"other":2}},{"custom":{"input_examples":["c"],"other":3}}]}`)
  308. wildcardOverride := map[string]interface{}{
  309. "operations": []interface{}{
  310. map[string]interface{}{
  311. "path": "tools.*.custom.input_examples",
  312. "mode": "delete",
  313. },
  314. },
  315. }
  316. indexedOverride := map[string]interface{}{
  317. "operations": lo.Map(lo.Range(3), func(index int, _ int) interface{} {
  318. return map[string]interface{}{
  319. "path": fmt.Sprintf("tools.%d.custom.input_examples", index),
  320. "mode": "delete",
  321. }
  322. }),
  323. }
  324. wildcardOut, err := ApplyParamOverride(input, wildcardOverride, nil)
  325. if err != nil {
  326. t.Fatalf("wildcard ApplyParamOverride returned error: %v", err)
  327. }
  328. indexedOut, err := ApplyParamOverride(input, indexedOverride, nil)
  329. if err != nil {
  330. t.Fatalf("indexed ApplyParamOverride returned error: %v", err)
  331. }
  332. assertJSONEqual(t, string(indexedOut), string(wildcardOut))
  333. }
  334. func TestApplyParamOverrideSetWildcardKeepOrigin(t *testing.T) {
  335. input := []byte(`{"tools":[{"custom":{"tag":"A"}},{"custom":{"tag":"B","enabled":false}},{"custom":{"tag":"C"}}]}`)
  336. override := map[string]interface{}{
  337. "operations": []interface{}{
  338. map[string]interface{}{
  339. "path": "tools.*.custom.enabled",
  340. "mode": "set",
  341. "value": true,
  342. "keep_origin": true,
  343. },
  344. },
  345. }
  346. out, err := ApplyParamOverride(input, override, nil)
  347. if err != nil {
  348. t.Fatalf("ApplyParamOverride returned error: %v", err)
  349. }
  350. var got struct {
  351. Tools []struct {
  352. Custom struct {
  353. Enabled bool `json:"enabled"`
  354. } `json:"custom"`
  355. } `json:"tools"`
  356. }
  357. if err := json.Unmarshal(out, &got); err != nil {
  358. t.Fatalf("failed to unmarshal output JSON: %v", err)
  359. }
  360. enabledValues := lo.Map(got.Tools, func(item struct {
  361. Custom struct {
  362. Enabled bool `json:"enabled"`
  363. } `json:"custom"`
  364. }, _ int) bool {
  365. return item.Custom.Enabled
  366. })
  367. if !reflect.DeepEqual(enabledValues, []bool{true, false, true}) {
  368. t.Fatalf("unexpected enabled values after wildcard keep_origin set: %v", enabledValues)
  369. }
  370. }
  371. func TestApplyParamOverrideTrimSpaceMultiWildcardPath(t *testing.T) {
  372. input := []byte(`{"tools":[{"custom":{"items":[{"name":" alpha "},{"name":" beta "}]}},{"custom":{"items":[{"name":" gamma"}]}}]}`)
  373. override := map[string]interface{}{
  374. "operations": []interface{}{
  375. map[string]interface{}{
  376. "path": "tools.*.custom.items.*.name",
  377. "mode": "trim_space",
  378. },
  379. },
  380. }
  381. out, err := ApplyParamOverride(input, override, nil)
  382. if err != nil {
  383. t.Fatalf("ApplyParamOverride returned error: %v", err)
  384. }
  385. var got struct {
  386. Tools []struct {
  387. Custom struct {
  388. Items []struct {
  389. Name string `json:"name"`
  390. } `json:"items"`
  391. } `json:"custom"`
  392. } `json:"tools"`
  393. }
  394. if err := json.Unmarshal(out, &got); err != nil {
  395. t.Fatalf("failed to unmarshal output JSON: %v", err)
  396. }
  397. names := lo.FlatMap(got.Tools, func(tool struct {
  398. Custom struct {
  399. Items []struct {
  400. Name string `json:"name"`
  401. } `json:"items"`
  402. } `json:"custom"`
  403. }, _ int) []string {
  404. return lo.Map(tool.Custom.Items, func(item struct {
  405. Name string `json:"name"`
  406. }, _ int) string {
  407. return item.Name
  408. })
  409. })
  410. if !reflect.DeepEqual(names, []string{"alpha", "beta", "gamma"}) {
  411. t.Fatalf("unexpected names after multi wildcard trim_space: %v", names)
  412. }
  413. }
  414. func TestApplyParamOverrideSet(t *testing.T) {
  415. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  416. override := map[string]interface{}{
  417. "operations": []interface{}{
  418. map[string]interface{}{
  419. "path": "temperature",
  420. "mode": "set",
  421. "value": 0.1,
  422. },
  423. },
  424. }
  425. out, err := ApplyParamOverride(input, override, nil)
  426. if err != nil {
  427. t.Fatalf("ApplyParamOverride returned error: %v", err)
  428. }
  429. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.1}`, string(out))
  430. }
  431. func TestApplyParamOverrideSetWithDescriptionKeepsCompatibility(t *testing.T) {
  432. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  433. overrideWithoutDesc := map[string]interface{}{
  434. "operations": []interface{}{
  435. map[string]interface{}{
  436. "path": "temperature",
  437. "mode": "set",
  438. "value": 0.1,
  439. },
  440. },
  441. }
  442. overrideWithDesc := map[string]interface{}{
  443. "operations": []interface{}{
  444. map[string]interface{}{
  445. "description": "set temperature for deterministic output",
  446. "path": "temperature",
  447. "mode": "set",
  448. "value": 0.1,
  449. },
  450. },
  451. }
  452. outWithoutDesc, err := ApplyParamOverride(input, overrideWithoutDesc, nil)
  453. if err != nil {
  454. t.Fatalf("ApplyParamOverride without description returned error: %v", err)
  455. }
  456. outWithDesc, err := ApplyParamOverride(input, overrideWithDesc, nil)
  457. if err != nil {
  458. t.Fatalf("ApplyParamOverride with description returned error: %v", err)
  459. }
  460. assertJSONEqual(t, string(outWithoutDesc), string(outWithDesc))
  461. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.1}`, string(outWithDesc))
  462. }
  463. func TestApplyParamOverrideSetKeepOrigin(t *testing.T) {
  464. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  465. override := map[string]interface{}{
  466. "operations": []interface{}{
  467. map[string]interface{}{
  468. "path": "temperature",
  469. "mode": "set",
  470. "value": 0.1,
  471. "keep_origin": true,
  472. },
  473. },
  474. }
  475. out, err := ApplyParamOverride(input, override, nil)
  476. if err != nil {
  477. t.Fatalf("ApplyParamOverride returned error: %v", err)
  478. }
  479. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
  480. }
  481. func TestApplyParamOverrideMove(t *testing.T) {
  482. input := []byte(`{"model":"gpt-4","meta":{"x":1}}`)
  483. override := map[string]interface{}{
  484. "operations": []interface{}{
  485. map[string]interface{}{
  486. "mode": "move",
  487. "from": "model",
  488. "to": "meta.model",
  489. },
  490. },
  491. }
  492. out, err := ApplyParamOverride(input, override, nil)
  493. if err != nil {
  494. t.Fatalf("ApplyParamOverride returned error: %v", err)
  495. }
  496. assertJSONEqual(t, `{"meta":{"x":1,"model":"gpt-4"}}`, string(out))
  497. }
  498. func TestApplyParamOverrideMoveMissingSource(t *testing.T) {
  499. input := []byte(`{"meta":{"x":1}}`)
  500. override := map[string]interface{}{
  501. "operations": []interface{}{
  502. map[string]interface{}{
  503. "mode": "move",
  504. "from": "model",
  505. "to": "meta.model",
  506. },
  507. },
  508. }
  509. _, err := ApplyParamOverride(input, override, nil)
  510. if err == nil {
  511. t.Fatalf("expected error, got nil")
  512. }
  513. }
  514. func TestApplyParamOverridePrependAppendString(t *testing.T) {
  515. input := []byte(`{"model":"gpt-4"}`)
  516. override := map[string]interface{}{
  517. "operations": []interface{}{
  518. map[string]interface{}{
  519. "path": "model",
  520. "mode": "prepend",
  521. "value": "openai/",
  522. },
  523. map[string]interface{}{
  524. "path": "model",
  525. "mode": "append",
  526. "value": "-latest",
  527. },
  528. },
  529. }
  530. out, err := ApplyParamOverride(input, override, nil)
  531. if err != nil {
  532. t.Fatalf("ApplyParamOverride returned error: %v", err)
  533. }
  534. assertJSONEqual(t, `{"model":"openai/gpt-4-latest"}`, string(out))
  535. }
  536. func TestApplyParamOverridePrependAppendArray(t *testing.T) {
  537. input := []byte(`{"arr":[1,2]}`)
  538. override := map[string]interface{}{
  539. "operations": []interface{}{
  540. map[string]interface{}{
  541. "path": "arr",
  542. "mode": "prepend",
  543. "value": 0,
  544. },
  545. map[string]interface{}{
  546. "path": "arr",
  547. "mode": "append",
  548. "value": []interface{}{3, 4},
  549. },
  550. },
  551. }
  552. out, err := ApplyParamOverride(input, override, nil)
  553. if err != nil {
  554. t.Fatalf("ApplyParamOverride returned error: %v", err)
  555. }
  556. assertJSONEqual(t, `{"arr":[0,1,2,3,4]}`, string(out))
  557. }
  558. func TestApplyParamOverrideAppendObjectMergeKeepOrigin(t *testing.T) {
  559. input := []byte(`{"obj":{"a":1}}`)
  560. override := map[string]interface{}{
  561. "operations": []interface{}{
  562. map[string]interface{}{
  563. "path": "obj",
  564. "mode": "append",
  565. "keep_origin": true,
  566. "value": map[string]interface{}{
  567. "a": 2,
  568. "b": 3,
  569. },
  570. },
  571. },
  572. }
  573. out, err := ApplyParamOverride(input, override, nil)
  574. if err != nil {
  575. t.Fatalf("ApplyParamOverride returned error: %v", err)
  576. }
  577. assertJSONEqual(t, `{"obj":{"a":1,"b":3}}`, string(out))
  578. }
  579. func TestApplyParamOverrideAppendObjectMergeOverride(t *testing.T) {
  580. input := []byte(`{"obj":{"a":1}}`)
  581. override := map[string]interface{}{
  582. "operations": []interface{}{
  583. map[string]interface{}{
  584. "path": "obj",
  585. "mode": "append",
  586. "value": map[string]interface{}{
  587. "a": 2,
  588. "b": 3,
  589. },
  590. },
  591. },
  592. }
  593. out, err := ApplyParamOverride(input, override, nil)
  594. if err != nil {
  595. t.Fatalf("ApplyParamOverride returned error: %v", err)
  596. }
  597. assertJSONEqual(t, `{"obj":{"a":2,"b":3}}`, string(out))
  598. }
  599. func TestApplyParamOverrideConditionORDefault(t *testing.T) {
  600. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  601. override := map[string]interface{}{
  602. "operations": []interface{}{
  603. map[string]interface{}{
  604. "path": "temperature",
  605. "mode": "set",
  606. "value": 0.1,
  607. "conditions": []interface{}{
  608. map[string]interface{}{
  609. "path": "model",
  610. "mode": "prefix",
  611. "value": "gpt",
  612. },
  613. map[string]interface{}{
  614. "path": "model",
  615. "mode": "prefix",
  616. "value": "claude",
  617. },
  618. },
  619. },
  620. },
  621. }
  622. out, err := ApplyParamOverride(input, override, nil)
  623. if err != nil {
  624. t.Fatalf("ApplyParamOverride returned error: %v", err)
  625. }
  626. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.1}`, string(out))
  627. }
  628. func TestApplyParamOverrideConditionAND(t *testing.T) {
  629. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  630. override := map[string]interface{}{
  631. "operations": []interface{}{
  632. map[string]interface{}{
  633. "path": "temperature",
  634. "mode": "set",
  635. "value": 0.1,
  636. "logic": "AND",
  637. "conditions": []interface{}{
  638. map[string]interface{}{
  639. "path": "model",
  640. "mode": "prefix",
  641. "value": "gpt",
  642. },
  643. map[string]interface{}{
  644. "path": "temperature",
  645. "mode": "gt",
  646. "value": 0.5,
  647. },
  648. },
  649. },
  650. },
  651. }
  652. out, err := ApplyParamOverride(input, override, nil)
  653. if err != nil {
  654. t.Fatalf("ApplyParamOverride returned error: %v", err)
  655. }
  656. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.1}`, string(out))
  657. }
  658. func TestApplyParamOverrideConditionInvert(t *testing.T) {
  659. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  660. override := map[string]interface{}{
  661. "operations": []interface{}{
  662. map[string]interface{}{
  663. "path": "temperature",
  664. "mode": "set",
  665. "value": 0.1,
  666. "conditions": []interface{}{
  667. map[string]interface{}{
  668. "path": "model",
  669. "mode": "prefix",
  670. "value": "gpt",
  671. "invert": true,
  672. },
  673. },
  674. },
  675. },
  676. }
  677. out, err := ApplyParamOverride(input, override, nil)
  678. if err != nil {
  679. t.Fatalf("ApplyParamOverride returned error: %v", err)
  680. }
  681. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
  682. }
  683. func TestApplyParamOverrideConditionPassMissingKey(t *testing.T) {
  684. input := []byte(`{"temperature":0.7}`)
  685. override := map[string]interface{}{
  686. "operations": []interface{}{
  687. map[string]interface{}{
  688. "path": "temperature",
  689. "mode": "set",
  690. "value": 0.1,
  691. "conditions": []interface{}{
  692. map[string]interface{}{
  693. "path": "model",
  694. "mode": "prefix",
  695. "value": "gpt",
  696. "pass_missing_key": true,
  697. },
  698. },
  699. },
  700. },
  701. }
  702. out, err := ApplyParamOverride(input, override, nil)
  703. if err != nil {
  704. t.Fatalf("ApplyParamOverride returned error: %v", err)
  705. }
  706. assertJSONEqual(t, `{"temperature":0.1}`, string(out))
  707. }
  708. func TestApplyParamOverrideConditionFromContext(t *testing.T) {
  709. input := []byte(`{"temperature":0.7}`)
  710. override := map[string]interface{}{
  711. "operations": []interface{}{
  712. map[string]interface{}{
  713. "path": "temperature",
  714. "mode": "set",
  715. "value": 0.1,
  716. "conditions": []interface{}{
  717. map[string]interface{}{
  718. "path": "model",
  719. "mode": "prefix",
  720. "value": "gpt",
  721. },
  722. },
  723. },
  724. },
  725. }
  726. ctx := map[string]interface{}{
  727. "model": "gpt-4",
  728. }
  729. out, err := ApplyParamOverride(input, override, ctx)
  730. if err != nil {
  731. t.Fatalf("ApplyParamOverride returned error: %v", err)
  732. }
  733. assertJSONEqual(t, `{"temperature":0.1}`, string(out))
  734. }
  735. func TestApplyParamOverrideNegativeIndexPath(t *testing.T) {
  736. input := []byte(`{"arr":[{"model":"a"},{"model":"b"}]}`)
  737. override := map[string]interface{}{
  738. "operations": []interface{}{
  739. map[string]interface{}{
  740. "path": "arr.-1.model",
  741. "mode": "set",
  742. "value": "c",
  743. },
  744. },
  745. }
  746. out, err := ApplyParamOverride(input, override, nil)
  747. if err != nil {
  748. t.Fatalf("ApplyParamOverride returned error: %v", err)
  749. }
  750. assertJSONEqual(t, `{"arr":[{"model":"a"},{"model":"c"}]}`, string(out))
  751. }
  752. func TestApplyParamOverrideRegexReplaceInvalidPattern(t *testing.T) {
  753. // regex_replace invalid pattern example:
  754. // {"operations":[{"path":"model","mode":"regex_replace","from":"(","to":"x"}]}
  755. input := []byte(`{"model":"gpt-4"}`)
  756. override := map[string]interface{}{
  757. "operations": []interface{}{
  758. map[string]interface{}{
  759. "path": "model",
  760. "mode": "regex_replace",
  761. "from": "(",
  762. "to": "x",
  763. },
  764. },
  765. }
  766. _, err := ApplyParamOverride(input, override, nil)
  767. if err == nil {
  768. t.Fatalf("expected error, got nil")
  769. }
  770. }
  771. func TestApplyParamOverrideCopy(t *testing.T) {
  772. // copy example:
  773. // {"operations":[{"mode":"copy","from":"model","to":"original_model"}]}
  774. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  775. override := map[string]interface{}{
  776. "operations": []interface{}{
  777. map[string]interface{}{
  778. "mode": "copy",
  779. "from": "model",
  780. "to": "original_model",
  781. },
  782. },
  783. }
  784. out, err := ApplyParamOverride(input, override, nil)
  785. if err != nil {
  786. t.Fatalf("ApplyParamOverride returned error: %v", err)
  787. }
  788. assertJSONEqual(t, `{"model":"gpt-4","original_model":"gpt-4","temperature":0.7}`, string(out))
  789. }
  790. func TestApplyParamOverrideCopyMissingSource(t *testing.T) {
  791. // copy missing source example:
  792. // {"operations":[{"mode":"copy","from":"model","to":"original_model"}]}
  793. input := []byte(`{"temperature":0.7}`)
  794. override := map[string]interface{}{
  795. "operations": []interface{}{
  796. map[string]interface{}{
  797. "mode": "copy",
  798. "from": "model",
  799. "to": "original_model",
  800. },
  801. },
  802. }
  803. _, err := ApplyParamOverride(input, override, nil)
  804. if err == nil {
  805. t.Fatalf("expected error, got nil")
  806. }
  807. }
  808. func TestApplyParamOverrideCopyRequiresFromTo(t *testing.T) {
  809. // copy requires from/to example:
  810. // {"operations":[{"mode":"copy"}]}
  811. input := []byte(`{"model":"gpt-4"}`)
  812. override := map[string]interface{}{
  813. "operations": []interface{}{
  814. map[string]interface{}{
  815. "mode": "copy",
  816. },
  817. },
  818. }
  819. _, err := ApplyParamOverride(input, override, nil)
  820. if err == nil {
  821. t.Fatalf("expected error, got nil")
  822. }
  823. }
  824. func TestApplyParamOverrideEnsurePrefix(t *testing.T) {
  825. // ensure_prefix example:
  826. // {"operations":[{"path":"model","mode":"ensure_prefix","value":"openai/"}]}
  827. input := []byte(`{"model":"gpt-4"}`)
  828. override := map[string]interface{}{
  829. "operations": []interface{}{
  830. map[string]interface{}{
  831. "path": "model",
  832. "mode": "ensure_prefix",
  833. "value": "openai/",
  834. },
  835. },
  836. }
  837. out, err := ApplyParamOverride(input, override, nil)
  838. if err != nil {
  839. t.Fatalf("ApplyParamOverride returned error: %v", err)
  840. }
  841. assertJSONEqual(t, `{"model":"openai/gpt-4"}`, string(out))
  842. }
  843. func TestApplyParamOverrideEnsurePrefixNoop(t *testing.T) {
  844. // ensure_prefix no-op example:
  845. // {"operations":[{"path":"model","mode":"ensure_prefix","value":"openai/"}]}
  846. input := []byte(`{"model":"openai/gpt-4"}`)
  847. override := map[string]interface{}{
  848. "operations": []interface{}{
  849. map[string]interface{}{
  850. "path": "model",
  851. "mode": "ensure_prefix",
  852. "value": "openai/",
  853. },
  854. },
  855. }
  856. out, err := ApplyParamOverride(input, override, nil)
  857. if err != nil {
  858. t.Fatalf("ApplyParamOverride returned error: %v", err)
  859. }
  860. assertJSONEqual(t, `{"model":"openai/gpt-4"}`, string(out))
  861. }
  862. func TestApplyParamOverrideEnsureSuffix(t *testing.T) {
  863. // ensure_suffix example:
  864. // {"operations":[{"path":"model","mode":"ensure_suffix","value":"-latest"}]}
  865. input := []byte(`{"model":"gpt-4"}`)
  866. override := map[string]interface{}{
  867. "operations": []interface{}{
  868. map[string]interface{}{
  869. "path": "model",
  870. "mode": "ensure_suffix",
  871. "value": "-latest",
  872. },
  873. },
  874. }
  875. out, err := ApplyParamOverride(input, override, nil)
  876. if err != nil {
  877. t.Fatalf("ApplyParamOverride returned error: %v", err)
  878. }
  879. assertJSONEqual(t, `{"model":"gpt-4-latest"}`, string(out))
  880. }
  881. func TestApplyParamOverrideEnsureSuffixNoop(t *testing.T) {
  882. // ensure_suffix no-op example:
  883. // {"operations":[{"path":"model","mode":"ensure_suffix","value":"-latest"}]}
  884. input := []byte(`{"model":"gpt-4-latest"}`)
  885. override := map[string]interface{}{
  886. "operations": []interface{}{
  887. map[string]interface{}{
  888. "path": "model",
  889. "mode": "ensure_suffix",
  890. "value": "-latest",
  891. },
  892. },
  893. }
  894. out, err := ApplyParamOverride(input, override, nil)
  895. if err != nil {
  896. t.Fatalf("ApplyParamOverride returned error: %v", err)
  897. }
  898. assertJSONEqual(t, `{"model":"gpt-4-latest"}`, string(out))
  899. }
  900. func TestApplyParamOverrideEnsureRequiresValue(t *testing.T) {
  901. // ensure_prefix requires value example:
  902. // {"operations":[{"path":"model","mode":"ensure_prefix"}]}
  903. input := []byte(`{"model":"gpt-4"}`)
  904. override := map[string]interface{}{
  905. "operations": []interface{}{
  906. map[string]interface{}{
  907. "path": "model",
  908. "mode": "ensure_prefix",
  909. },
  910. },
  911. }
  912. _, err := ApplyParamOverride(input, override, nil)
  913. if err == nil {
  914. t.Fatalf("expected error, got nil")
  915. }
  916. }
  917. func TestApplyParamOverrideTrimSpace(t *testing.T) {
  918. // trim_space example:
  919. // {"operations":[{"path":"model","mode":"trim_space"}]}
  920. input := []byte("{\"model\":\" gpt-4 \\n\"}")
  921. override := map[string]interface{}{
  922. "operations": []interface{}{
  923. map[string]interface{}{
  924. "path": "model",
  925. "mode": "trim_space",
  926. },
  927. },
  928. }
  929. out, err := ApplyParamOverride(input, override, nil)
  930. if err != nil {
  931. t.Fatalf("ApplyParamOverride returned error: %v", err)
  932. }
  933. assertJSONEqual(t, `{"model":"gpt-4"}`, string(out))
  934. }
  935. func TestApplyParamOverrideToLower(t *testing.T) {
  936. // to_lower example:
  937. // {"operations":[{"path":"model","mode":"to_lower"}]}
  938. input := []byte(`{"model":"GPT-4"}`)
  939. override := map[string]interface{}{
  940. "operations": []interface{}{
  941. map[string]interface{}{
  942. "path": "model",
  943. "mode": "to_lower",
  944. },
  945. },
  946. }
  947. out, err := ApplyParamOverride(input, override, nil)
  948. if err != nil {
  949. t.Fatalf("ApplyParamOverride returned error: %v", err)
  950. }
  951. assertJSONEqual(t, `{"model":"gpt-4"}`, string(out))
  952. }
  953. func TestApplyParamOverrideToUpper(t *testing.T) {
  954. // to_upper example:
  955. // {"operations":[{"path":"model","mode":"to_upper"}]}
  956. input := []byte(`{"model":"gpt-4"}`)
  957. override := map[string]interface{}{
  958. "operations": []interface{}{
  959. map[string]interface{}{
  960. "path": "model",
  961. "mode": "to_upper",
  962. },
  963. },
  964. }
  965. out, err := ApplyParamOverride(input, override, nil)
  966. if err != nil {
  967. t.Fatalf("ApplyParamOverride returned error: %v", err)
  968. }
  969. assertJSONEqual(t, `{"model":"GPT-4"}`, string(out))
  970. }
  971. func TestApplyParamOverrideReturnError(t *testing.T) {
  972. input := []byte(`{"model":"gemini-2.5-pro"}`)
  973. override := map[string]interface{}{
  974. "operations": []interface{}{
  975. map[string]interface{}{
  976. "mode": "return_error",
  977. "value": map[string]interface{}{
  978. "message": "forced bad request by param override",
  979. "status_code": 422,
  980. "code": "forced_bad_request",
  981. "type": "invalid_request_error",
  982. "skip_retry": true,
  983. },
  984. "conditions": []interface{}{
  985. map[string]interface{}{
  986. "path": "retry.is_retry",
  987. "mode": "full",
  988. "value": true,
  989. },
  990. },
  991. },
  992. },
  993. }
  994. ctx := map[string]interface{}{
  995. "retry": map[string]interface{}{
  996. "index": 1,
  997. "is_retry": true,
  998. },
  999. }
  1000. _, err := ApplyParamOverride(input, override, ctx)
  1001. if err == nil {
  1002. t.Fatalf("expected error, got nil")
  1003. }
  1004. returnErr, ok := AsParamOverrideReturnError(err)
  1005. if !ok {
  1006. t.Fatalf("expected ParamOverrideReturnError, got %T: %v", err, err)
  1007. }
  1008. if returnErr.StatusCode != 422 {
  1009. t.Fatalf("expected status 422, got %d", returnErr.StatusCode)
  1010. }
  1011. if returnErr.Code != "forced_bad_request" {
  1012. t.Fatalf("expected code forced_bad_request, got %s", returnErr.Code)
  1013. }
  1014. if !returnErr.SkipRetry {
  1015. t.Fatalf("expected skip_retry true")
  1016. }
  1017. }
  1018. func TestApplyParamOverridePruneObjectsByTypeString(t *testing.T) {
  1019. input := []byte(`{
  1020. "messages":[
  1021. {"role":"assistant","content":[
  1022. {"type":"output_text","text":"a"},
  1023. {"type":"redacted_thinking","text":"secret"},
  1024. {"type":"tool_call","name":"tool_a"}
  1025. ]},
  1026. {"role":"assistant","content":[
  1027. {"type":"output_text","text":"b"},
  1028. {"type":"wrapper","parts":[
  1029. {"type":"redacted_thinking","text":"secret2"},
  1030. {"type":"output_text","text":"c"}
  1031. ]}
  1032. ]}
  1033. ]
  1034. }`)
  1035. override := map[string]interface{}{
  1036. "operations": []interface{}{
  1037. map[string]interface{}{
  1038. "mode": "prune_objects",
  1039. "value": "redacted_thinking",
  1040. },
  1041. },
  1042. }
  1043. out, err := ApplyParamOverride(input, override, nil)
  1044. if err != nil {
  1045. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1046. }
  1047. assertJSONEqual(t, `{
  1048. "messages":[
  1049. {"role":"assistant","content":[
  1050. {"type":"output_text","text":"a"},
  1051. {"type":"tool_call","name":"tool_a"}
  1052. ]},
  1053. {"role":"assistant","content":[
  1054. {"type":"output_text","text":"b"},
  1055. {"type":"wrapper","parts":[
  1056. {"type":"output_text","text":"c"}
  1057. ]}
  1058. ]}
  1059. ]
  1060. }`, string(out))
  1061. }
  1062. func TestApplyParamOverridePruneObjectsWhereAndPath(t *testing.T) {
  1063. input := []byte(`{
  1064. "a":{"items":[{"type":"redacted_thinking","id":1},{"type":"output_text","id":2}]},
  1065. "b":{"items":[{"type":"redacted_thinking","id":3},{"type":"output_text","id":4}]}
  1066. }`)
  1067. override := map[string]interface{}{
  1068. "operations": []interface{}{
  1069. map[string]interface{}{
  1070. "path": "a",
  1071. "mode": "prune_objects",
  1072. "value": map[string]interface{}{
  1073. "where": map[string]interface{}{
  1074. "type": "redacted_thinking",
  1075. },
  1076. },
  1077. },
  1078. },
  1079. }
  1080. out, err := ApplyParamOverride(input, override, nil)
  1081. if err != nil {
  1082. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1083. }
  1084. assertJSONEqual(t, `{
  1085. "a":{"items":[{"type":"output_text","id":2}]},
  1086. "b":{"items":[{"type":"redacted_thinking","id":3},{"type":"output_text","id":4}]}
  1087. }`, string(out))
  1088. }
  1089. func TestApplyParamOverrideNormalizeThinkingSignatureUnsupported(t *testing.T) {
  1090. input := []byte(`{"items":[{"type":"redacted_thinking"}]}`)
  1091. override := map[string]interface{}{
  1092. "operations": []interface{}{
  1093. map[string]interface{}{
  1094. "mode": "normalize_thinking_signature",
  1095. },
  1096. },
  1097. }
  1098. _, err := ApplyParamOverride(input, override, nil)
  1099. if err == nil {
  1100. t.Fatalf("expected error, got nil")
  1101. }
  1102. }
  1103. func TestApplyParamOverrideConditionFromRetryAndLastErrorContext(t *testing.T) {
  1104. info := &RelayInfo{
  1105. RetryIndex: 1,
  1106. LastError: types.WithOpenAIError(types.OpenAIError{
  1107. Message: "invalid thinking signature",
  1108. Type: "invalid_request_error",
  1109. Code: "bad_thought_signature",
  1110. }, 400),
  1111. }
  1112. ctx := BuildParamOverrideContext(info)
  1113. input := []byte(`{"temperature":0.7}`)
  1114. override := map[string]interface{}{
  1115. "operations": []interface{}{
  1116. map[string]interface{}{
  1117. "path": "temperature",
  1118. "mode": "set",
  1119. "value": 0.1,
  1120. "logic": "AND",
  1121. "conditions": []interface{}{
  1122. map[string]interface{}{
  1123. "path": "is_retry",
  1124. "mode": "full",
  1125. "value": true,
  1126. },
  1127. map[string]interface{}{
  1128. "path": "last_error.code",
  1129. "mode": "contains",
  1130. "value": "thought_signature",
  1131. },
  1132. },
  1133. },
  1134. },
  1135. }
  1136. out, err := ApplyParamOverride(input, override, ctx)
  1137. if err != nil {
  1138. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1139. }
  1140. assertJSONEqual(t, `{"temperature":0.1}`, string(out))
  1141. }
  1142. func TestApplyParamOverrideConditionFromRequestHeaders(t *testing.T) {
  1143. input := []byte(`{"temperature":0.7}`)
  1144. override := map[string]interface{}{
  1145. "operations": []interface{}{
  1146. map[string]interface{}{
  1147. "path": "temperature",
  1148. "mode": "set",
  1149. "value": 0.1,
  1150. "conditions": []interface{}{
  1151. map[string]interface{}{
  1152. "path": "request_headers.authorization",
  1153. "mode": "contains",
  1154. "value": "Bearer ",
  1155. },
  1156. },
  1157. },
  1158. },
  1159. }
  1160. ctx := map[string]interface{}{
  1161. "request_headers": map[string]interface{}{
  1162. "authorization": "Bearer token-123",
  1163. },
  1164. }
  1165. out, err := ApplyParamOverride(input, override, ctx)
  1166. if err != nil {
  1167. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1168. }
  1169. assertJSONEqual(t, `{"temperature":0.1}`, string(out))
  1170. }
  1171. func TestApplyParamOverrideSetHeaderAndUseInLaterCondition(t *testing.T) {
  1172. input := []byte(`{"temperature":0.7}`)
  1173. override := map[string]interface{}{
  1174. "operations": []interface{}{
  1175. map[string]interface{}{
  1176. "mode": "set_header",
  1177. "path": "X-Debug-Mode",
  1178. "value": "enabled",
  1179. },
  1180. map[string]interface{}{
  1181. "path": "temperature",
  1182. "mode": "set",
  1183. "value": 0.1,
  1184. "conditions": []interface{}{
  1185. map[string]interface{}{
  1186. "path": "header_override.x-debug-mode",
  1187. "mode": "full",
  1188. "value": "enabled",
  1189. },
  1190. },
  1191. },
  1192. },
  1193. }
  1194. out, err := ApplyParamOverride(input, override, nil)
  1195. if err != nil {
  1196. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1197. }
  1198. assertJSONEqual(t, `{"temperature":0.1}`, string(out))
  1199. }
  1200. func TestApplyParamOverrideCopyHeaderFromRequestHeaders(t *testing.T) {
  1201. input := []byte(`{"temperature":0.7}`)
  1202. override := map[string]interface{}{
  1203. "operations": []interface{}{
  1204. map[string]interface{}{
  1205. "mode": "copy_header",
  1206. "from": "Authorization",
  1207. "to": "X-Upstream-Auth",
  1208. },
  1209. map[string]interface{}{
  1210. "path": "temperature",
  1211. "mode": "set",
  1212. "value": 0.1,
  1213. "conditions": []interface{}{
  1214. map[string]interface{}{
  1215. "path": "header_override.x-upstream-auth",
  1216. "mode": "contains",
  1217. "value": "Bearer ",
  1218. },
  1219. },
  1220. },
  1221. },
  1222. }
  1223. ctx := map[string]interface{}{
  1224. "request_headers": map[string]interface{}{
  1225. "authorization": "Bearer token-123",
  1226. },
  1227. }
  1228. out, err := ApplyParamOverride(input, override, ctx)
  1229. if err != nil {
  1230. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1231. }
  1232. assertJSONEqual(t, `{"temperature":0.1}`, string(out))
  1233. }
  1234. func TestApplyParamOverridePassHeadersSkipsMissingHeaders(t *testing.T) {
  1235. input := []byte(`{"temperature":0.7}`)
  1236. override := map[string]interface{}{
  1237. "operations": []interface{}{
  1238. map[string]interface{}{
  1239. "mode": "pass_headers",
  1240. "value": []interface{}{"X-Codex-Beta-Features", "Session_id"},
  1241. },
  1242. },
  1243. }
  1244. ctx := map[string]interface{}{
  1245. "request_headers": map[string]interface{}{
  1246. "session_id": "sess-123",
  1247. },
  1248. }
  1249. out, err := ApplyParamOverride(input, override, ctx)
  1250. if err != nil {
  1251. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1252. }
  1253. assertJSONEqual(t, `{"temperature":0.7}`, string(out))
  1254. headers, ok := ctx["header_override"].(map[string]interface{})
  1255. if !ok {
  1256. t.Fatalf("expected header_override context map")
  1257. }
  1258. if headers["session_id"] != "sess-123" {
  1259. t.Fatalf("expected session_id to be passed, got: %v", headers["session_id"])
  1260. }
  1261. if _, exists := headers["x-codex-beta-features"]; exists {
  1262. t.Fatalf("expected missing header to be skipped")
  1263. }
  1264. }
  1265. func TestApplyParamOverrideCopyHeaderSkipsMissingSource(t *testing.T) {
  1266. input := []byte(`{"temperature":0.7}`)
  1267. override := map[string]interface{}{
  1268. "operations": []interface{}{
  1269. map[string]interface{}{
  1270. "mode": "copy_header",
  1271. "from": "X-Missing-Header",
  1272. "to": "X-Upstream-Auth",
  1273. },
  1274. },
  1275. }
  1276. ctx := map[string]interface{}{
  1277. "request_headers": map[string]interface{}{
  1278. "authorization": "Bearer token-123",
  1279. },
  1280. }
  1281. out, err := ApplyParamOverride(input, override, ctx)
  1282. if err != nil {
  1283. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1284. }
  1285. assertJSONEqual(t, `{"temperature":0.7}`, string(out))
  1286. headers, ok := ctx["header_override"].(map[string]interface{})
  1287. if !ok {
  1288. return
  1289. }
  1290. if _, exists := headers["x-upstream-auth"]; exists {
  1291. t.Fatalf("expected X-Upstream-Auth to be skipped when source header is missing")
  1292. }
  1293. }
  1294. func TestApplyParamOverrideMoveHeaderSkipsMissingSource(t *testing.T) {
  1295. input := []byte(`{"temperature":0.7}`)
  1296. override := map[string]interface{}{
  1297. "operations": []interface{}{
  1298. map[string]interface{}{
  1299. "mode": "move_header",
  1300. "from": "X-Missing-Header",
  1301. "to": "X-Upstream-Auth",
  1302. },
  1303. },
  1304. }
  1305. ctx := map[string]interface{}{
  1306. "request_headers": map[string]interface{}{
  1307. "authorization": "Bearer token-123",
  1308. },
  1309. }
  1310. out, err := ApplyParamOverride(input, override, ctx)
  1311. if err != nil {
  1312. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1313. }
  1314. assertJSONEqual(t, `{"temperature":0.7}`, string(out))
  1315. headers, ok := ctx["header_override"].(map[string]interface{})
  1316. if !ok {
  1317. return
  1318. }
  1319. if _, exists := headers["x-upstream-auth"]; exists {
  1320. t.Fatalf("expected X-Upstream-Auth to be skipped when source header is missing")
  1321. }
  1322. }
  1323. func TestApplyParamOverrideSyncFieldsHeaderToJSON(t *testing.T) {
  1324. input := []byte(`{"model":"gpt-4"}`)
  1325. override := map[string]interface{}{
  1326. "operations": []interface{}{
  1327. map[string]interface{}{
  1328. "mode": "sync_fields",
  1329. "from": "header:session_id",
  1330. "to": "json:prompt_cache_key",
  1331. },
  1332. },
  1333. }
  1334. ctx := map[string]interface{}{
  1335. "request_headers": map[string]interface{}{
  1336. "session_id": "sess-123",
  1337. },
  1338. }
  1339. out, err := ApplyParamOverride(input, override, ctx)
  1340. if err != nil {
  1341. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1342. }
  1343. assertJSONEqual(t, `{"model":"gpt-4","prompt_cache_key":"sess-123"}`, string(out))
  1344. }
  1345. func TestApplyParamOverrideSyncFieldsJSONToHeader(t *testing.T) {
  1346. input := []byte(`{"model":"gpt-4","prompt_cache_key":"cache-abc"}`)
  1347. override := map[string]interface{}{
  1348. "operations": []interface{}{
  1349. map[string]interface{}{
  1350. "mode": "sync_fields",
  1351. "from": "header:session_id",
  1352. "to": "json:prompt_cache_key",
  1353. },
  1354. },
  1355. }
  1356. ctx := map[string]interface{}{}
  1357. out, err := ApplyParamOverride(input, override, ctx)
  1358. if err != nil {
  1359. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1360. }
  1361. assertJSONEqual(t, `{"model":"gpt-4","prompt_cache_key":"cache-abc"}`, string(out))
  1362. headers, ok := ctx["header_override"].(map[string]interface{})
  1363. if !ok {
  1364. t.Fatalf("expected header_override context map")
  1365. }
  1366. if headers["session_id"] != "cache-abc" {
  1367. t.Fatalf("expected session_id to be synced from prompt_cache_key, got: %v", headers["session_id"])
  1368. }
  1369. }
  1370. func TestApplyParamOverrideSyncFieldsNoChangeWhenBothExist(t *testing.T) {
  1371. input := []byte(`{"model":"gpt-4","prompt_cache_key":"cache-body"}`)
  1372. override := map[string]interface{}{
  1373. "operations": []interface{}{
  1374. map[string]interface{}{
  1375. "mode": "sync_fields",
  1376. "from": "header:session_id",
  1377. "to": "json:prompt_cache_key",
  1378. },
  1379. },
  1380. }
  1381. ctx := map[string]interface{}{
  1382. "request_headers": map[string]interface{}{
  1383. "session_id": "cache-header",
  1384. },
  1385. }
  1386. out, err := ApplyParamOverride(input, override, ctx)
  1387. if err != nil {
  1388. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1389. }
  1390. assertJSONEqual(t, `{"model":"gpt-4","prompt_cache_key":"cache-body"}`, string(out))
  1391. headers, _ := ctx["header_override"].(map[string]interface{})
  1392. if headers != nil {
  1393. if _, exists := headers["session_id"]; exists {
  1394. t.Fatalf("expected no override when both sides already have value")
  1395. }
  1396. }
  1397. }
  1398. func TestApplyParamOverrideSyncFieldsInvalidTarget(t *testing.T) {
  1399. input := []byte(`{"model":"gpt-4"}`)
  1400. override := map[string]interface{}{
  1401. "operations": []interface{}{
  1402. map[string]interface{}{
  1403. "mode": "sync_fields",
  1404. "from": "foo:session_id",
  1405. "to": "json:prompt_cache_key",
  1406. },
  1407. },
  1408. }
  1409. _, err := ApplyParamOverride(input, override, nil)
  1410. if err == nil {
  1411. t.Fatalf("expected error, got nil")
  1412. }
  1413. }
  1414. func TestApplyParamOverrideSetHeaderKeepOrigin(t *testing.T) {
  1415. input := []byte(`{"temperature":0.7}`)
  1416. override := map[string]interface{}{
  1417. "operations": []interface{}{
  1418. map[string]interface{}{
  1419. "mode": "set_header",
  1420. "path": "X-Feature-Flag",
  1421. "value": "new-value",
  1422. "keep_origin": true,
  1423. },
  1424. },
  1425. }
  1426. ctx := map[string]interface{}{
  1427. "header_override": map[string]interface{}{
  1428. "x-feature-flag": "legacy-value",
  1429. },
  1430. }
  1431. _, err := ApplyParamOverride(input, override, ctx)
  1432. if err != nil {
  1433. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1434. }
  1435. headers, ok := ctx["header_override"].(map[string]interface{})
  1436. if !ok {
  1437. t.Fatalf("expected header_override context map")
  1438. }
  1439. if headers["x-feature-flag"] != "legacy-value" {
  1440. t.Fatalf("expected keep_origin to preserve old value, got: %v", headers["x-feature-flag"])
  1441. }
  1442. }
  1443. func TestApplyParamOverrideSetHeaderMapRewritesCommaSeparatedHeader(t *testing.T) {
  1444. input := []byte(`{"temperature":0.7}`)
  1445. override := map[string]interface{}{
  1446. "operations": []interface{}{
  1447. map[string]interface{}{
  1448. "mode": "set_header",
  1449. "path": "anthropic-beta",
  1450. "value": map[string]interface{}{
  1451. "advanced-tool-use-2025-11-20": nil,
  1452. "computer-use-2025-01-24": "computer-use-2025-01-24",
  1453. },
  1454. },
  1455. },
  1456. }
  1457. ctx := map[string]interface{}{
  1458. "request_headers": map[string]interface{}{
  1459. "anthropic-beta": "advanced-tool-use-2025-11-20, computer-use-2025-01-24",
  1460. },
  1461. }
  1462. _, err := ApplyParamOverride(input, override, ctx)
  1463. if err != nil {
  1464. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1465. }
  1466. headers, ok := ctx["header_override"].(map[string]interface{})
  1467. if !ok {
  1468. t.Fatalf("expected header_override context map")
  1469. }
  1470. if headers["anthropic-beta"] != "computer-use-2025-01-24" {
  1471. t.Fatalf("expected anthropic-beta to keep only mapped value, got: %v", headers["anthropic-beta"])
  1472. }
  1473. }
  1474. func TestApplyParamOverrideSetHeaderMapDeleteWholeHeaderWhenAllTokensCleared(t *testing.T) {
  1475. input := []byte(`{"temperature":0.7}`)
  1476. override := map[string]interface{}{
  1477. "operations": []interface{}{
  1478. map[string]interface{}{
  1479. "mode": "set_header",
  1480. "path": "anthropic-beta",
  1481. "value": map[string]interface{}{
  1482. "advanced-tool-use-2025-11-20": nil,
  1483. "computer-use-2025-01-24": nil,
  1484. },
  1485. },
  1486. },
  1487. }
  1488. ctx := map[string]interface{}{
  1489. "header_override": map[string]interface{}{
  1490. "anthropic-beta": "advanced-tool-use-2025-11-20,computer-use-2025-01-24",
  1491. },
  1492. }
  1493. _, err := ApplyParamOverride(input, override, ctx)
  1494. if err != nil {
  1495. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1496. }
  1497. headers, ok := ctx["header_override"].(map[string]interface{})
  1498. if !ok {
  1499. t.Fatalf("expected header_override context map")
  1500. }
  1501. if _, exists := headers["anthropic-beta"]; exists {
  1502. t.Fatalf("expected anthropic-beta to be deleted when all mapped values are null")
  1503. }
  1504. }
  1505. func TestApplyParamOverrideConditionsObjectShorthand(t *testing.T) {
  1506. input := []byte(`{"temperature":0.7}`)
  1507. override := map[string]interface{}{
  1508. "operations": []interface{}{
  1509. map[string]interface{}{
  1510. "path": "temperature",
  1511. "mode": "set",
  1512. "value": 0.1,
  1513. "logic": "AND",
  1514. "conditions": map[string]interface{}{
  1515. "is_retry": true,
  1516. "last_error.status_code": 400.0,
  1517. },
  1518. },
  1519. },
  1520. }
  1521. ctx := map[string]interface{}{
  1522. "is_retry": true,
  1523. "last_error": map[string]interface{}{
  1524. "status_code": 400.0,
  1525. },
  1526. }
  1527. out, err := ApplyParamOverride(input, override, ctx)
  1528. if err != nil {
  1529. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1530. }
  1531. assertJSONEqual(t, `{"temperature":0.1}`, string(out))
  1532. }
  1533. func TestApplyParamOverrideWithRelayInfoSyncRuntimeHeaders(t *testing.T) {
  1534. info := &RelayInfo{
  1535. ChannelMeta: &ChannelMeta{
  1536. ParamOverride: map[string]interface{}{
  1537. "operations": []interface{}{
  1538. map[string]interface{}{
  1539. "mode": "set_header",
  1540. "path": "X-Injected-By-Param-Override",
  1541. "value": "enabled",
  1542. },
  1543. map[string]interface{}{
  1544. "mode": "delete_header",
  1545. "path": "X-Delete-Me",
  1546. },
  1547. },
  1548. },
  1549. HeadersOverride: map[string]interface{}{
  1550. "X-Delete-Me": "legacy",
  1551. "X-Keep-Me": "keep",
  1552. },
  1553. },
  1554. }
  1555. input := []byte(`{"temperature":0.7}`)
  1556. out, err := ApplyParamOverrideWithRelayInfo(input, info)
  1557. if err != nil {
  1558. t.Fatalf("ApplyParamOverrideWithRelayInfo returned error: %v", err)
  1559. }
  1560. assertJSONEqual(t, `{"temperature":0.7}`, string(out))
  1561. if !info.UseRuntimeHeadersOverride {
  1562. t.Fatalf("expected runtime header override to be enabled")
  1563. }
  1564. if info.RuntimeHeadersOverride["x-keep-me"] != "keep" {
  1565. t.Fatalf("expected x-keep-me header to be preserved, got: %v", info.RuntimeHeadersOverride["x-keep-me"])
  1566. }
  1567. if info.RuntimeHeadersOverride["x-injected-by-param-override"] != "enabled" {
  1568. t.Fatalf("expected x-injected-by-param-override header to be set, got: %v", info.RuntimeHeadersOverride["x-injected-by-param-override"])
  1569. }
  1570. if _, exists := info.RuntimeHeadersOverride["x-delete-me"]; exists {
  1571. t.Fatalf("expected x-delete-me header to be deleted")
  1572. }
  1573. }
  1574. func TestApplyParamOverrideWithRelayInfoMixedLegacyAndOperations(t *testing.T) {
  1575. info := &RelayInfo{
  1576. RequestHeaders: map[string]string{
  1577. "Originator": "Codex CLI",
  1578. },
  1579. ChannelMeta: &ChannelMeta{
  1580. ParamOverride: map[string]interface{}{
  1581. "temperature": 0.2,
  1582. "operations": []interface{}{
  1583. map[string]interface{}{
  1584. "mode": "pass_headers",
  1585. "value": []interface{}{"Originator"},
  1586. },
  1587. },
  1588. },
  1589. HeadersOverride: map[string]interface{}{
  1590. "X-Static": "legacy-static",
  1591. },
  1592. },
  1593. }
  1594. out, err := ApplyParamOverrideWithRelayInfo([]byte(`{"model":"gpt-5","temperature":0.7}`), info)
  1595. if err != nil {
  1596. t.Fatalf("ApplyParamOverrideWithRelayInfo returned error: %v", err)
  1597. }
  1598. assertJSONEqual(t, `{"model":"gpt-5","temperature":0.2}`, string(out))
  1599. if !info.UseRuntimeHeadersOverride {
  1600. t.Fatalf("expected runtime header override to be enabled")
  1601. }
  1602. if info.RuntimeHeadersOverride["x-static"] != "legacy-static" {
  1603. t.Fatalf("expected x-static to be preserved, got: %v", info.RuntimeHeadersOverride["x-static"])
  1604. }
  1605. if info.RuntimeHeadersOverride["originator"] != "Codex CLI" {
  1606. t.Fatalf("expected originator header to be passed, got: %v", info.RuntimeHeadersOverride["originator"])
  1607. }
  1608. }
  1609. func TestApplyParamOverrideWithRelayInfoMoveAndCopyHeaders(t *testing.T) {
  1610. info := &RelayInfo{
  1611. ChannelMeta: &ChannelMeta{
  1612. ParamOverride: map[string]interface{}{
  1613. "operations": []interface{}{
  1614. map[string]interface{}{
  1615. "mode": "move_header",
  1616. "from": "X-Legacy-Trace",
  1617. "to": "X-Trace",
  1618. },
  1619. map[string]interface{}{
  1620. "mode": "copy_header",
  1621. "from": "X-Trace",
  1622. "to": "X-Trace-Backup",
  1623. },
  1624. },
  1625. },
  1626. HeadersOverride: map[string]interface{}{
  1627. "X-Legacy-Trace": "trace-123",
  1628. },
  1629. },
  1630. }
  1631. input := []byte(`{"temperature":0.7}`)
  1632. _, err := ApplyParamOverrideWithRelayInfo(input, info)
  1633. if err != nil {
  1634. t.Fatalf("ApplyParamOverrideWithRelayInfo returned error: %v", err)
  1635. }
  1636. if _, exists := info.RuntimeHeadersOverride["x-legacy-trace"]; exists {
  1637. t.Fatalf("expected source header to be removed after move")
  1638. }
  1639. if info.RuntimeHeadersOverride["x-trace"] != "trace-123" {
  1640. t.Fatalf("expected x-trace to be set, got: %v", info.RuntimeHeadersOverride["x-trace"])
  1641. }
  1642. if info.RuntimeHeadersOverride["x-trace-backup"] != "trace-123" {
  1643. t.Fatalf("expected x-trace-backup to be copied, got: %v", info.RuntimeHeadersOverride["x-trace-backup"])
  1644. }
  1645. }
  1646. func TestApplyParamOverrideWithRelayInfoSetHeaderMapRewritesAnthropicBeta(t *testing.T) {
  1647. info := &RelayInfo{
  1648. ChannelMeta: &ChannelMeta{
  1649. ParamOverride: map[string]interface{}{
  1650. "operations": []interface{}{
  1651. map[string]interface{}{
  1652. "mode": "set_header",
  1653. "path": "anthropic-beta",
  1654. "value": map[string]interface{}{
  1655. "advanced-tool-use-2025-11-20": nil,
  1656. "computer-use-2025-01-24": "computer-use-2025-01-24",
  1657. },
  1658. },
  1659. },
  1660. },
  1661. HeadersOverride: map[string]interface{}{
  1662. "anthropic-beta": "advanced-tool-use-2025-11-20, computer-use-2025-01-24",
  1663. },
  1664. },
  1665. }
  1666. _, err := ApplyParamOverrideWithRelayInfo([]byte(`{"temperature":0.7}`), info)
  1667. if err != nil {
  1668. t.Fatalf("ApplyParamOverrideWithRelayInfo returned error: %v", err)
  1669. }
  1670. if !info.UseRuntimeHeadersOverride {
  1671. t.Fatalf("expected runtime header override to be enabled")
  1672. }
  1673. if info.RuntimeHeadersOverride["anthropic-beta"] != "computer-use-2025-01-24" {
  1674. t.Fatalf("expected anthropic-beta to be rewritten, got: %v", info.RuntimeHeadersOverride["anthropic-beta"])
  1675. }
  1676. }
  1677. func TestGetEffectiveHeaderOverrideUsesRuntimeOverrideAsFinalResult(t *testing.T) {
  1678. info := &RelayInfo{
  1679. UseRuntimeHeadersOverride: true,
  1680. RuntimeHeadersOverride: map[string]interface{}{
  1681. "x-runtime": "runtime-only",
  1682. },
  1683. ChannelMeta: &ChannelMeta{
  1684. HeadersOverride: map[string]interface{}{
  1685. "X-Static": "static-value",
  1686. "X-Deleted": "should-not-exist",
  1687. },
  1688. },
  1689. }
  1690. effective := GetEffectiveHeaderOverride(info)
  1691. if effective["x-runtime"] != "runtime-only" {
  1692. t.Fatalf("expected x-runtime from runtime override, got: %v", effective["x-runtime"])
  1693. }
  1694. if _, exists := effective["x-static"]; exists {
  1695. t.Fatalf("expected runtime override to be final and not merge channel headers")
  1696. }
  1697. }
  1698. func TestRemoveDisabledFieldsSkipWhenChannelPassThroughEnabled(t *testing.T) {
  1699. input := `{
  1700. "service_tier":"flex",
  1701. "safety_identifier":"user-123",
  1702. "store":true,
  1703. "stream_options":{"include_obfuscation":false}
  1704. }`
  1705. settings := dto.ChannelOtherSettings{}
  1706. out, err := RemoveDisabledFields([]byte(input), settings, true)
  1707. if err != nil {
  1708. t.Fatalf("RemoveDisabledFields returned error: %v", err)
  1709. }
  1710. assertJSONEqual(t, input, string(out))
  1711. }
  1712. func TestRemoveDisabledFieldsSkipWhenGlobalPassThroughEnabled(t *testing.T) {
  1713. original := model_setting.GetGlobalSettings().PassThroughRequestEnabled
  1714. model_setting.GetGlobalSettings().PassThroughRequestEnabled = true
  1715. t.Cleanup(func() {
  1716. model_setting.GetGlobalSettings().PassThroughRequestEnabled = original
  1717. })
  1718. input := `{
  1719. "service_tier":"flex",
  1720. "safety_identifier":"user-123",
  1721. "stream_options":{"include_obfuscation":false}
  1722. }`
  1723. settings := dto.ChannelOtherSettings{}
  1724. out, err := RemoveDisabledFields([]byte(input), settings, false)
  1725. if err != nil {
  1726. t.Fatalf("RemoveDisabledFields returned error: %v", err)
  1727. }
  1728. assertJSONEqual(t, input, string(out))
  1729. }
  1730. func TestRemoveDisabledFieldsDefaultFiltering(t *testing.T) {
  1731. input := `{
  1732. "service_tier":"flex",
  1733. "inference_geo":"eu",
  1734. "safety_identifier":"user-123",
  1735. "store":true,
  1736. "stream_options":{"include_obfuscation":false}
  1737. }`
  1738. settings := dto.ChannelOtherSettings{}
  1739. out, err := RemoveDisabledFields([]byte(input), settings, false)
  1740. if err != nil {
  1741. t.Fatalf("RemoveDisabledFields returned error: %v", err)
  1742. }
  1743. assertJSONEqual(t, `{"store":true}`, string(out))
  1744. }
  1745. func TestRemoveDisabledFieldsAllowInferenceGeo(t *testing.T) {
  1746. input := `{
  1747. "inference_geo":"eu",
  1748. "store":true
  1749. }`
  1750. settings := dto.ChannelOtherSettings{
  1751. AllowInferenceGeo: true,
  1752. }
  1753. out, err := RemoveDisabledFields([]byte(input), settings, false)
  1754. if err != nil {
  1755. t.Fatalf("RemoveDisabledFields returned error: %v", err)
  1756. }
  1757. assertJSONEqual(t, `{"inference_geo":"eu","store":true}`, string(out))
  1758. }
  1759. func assertJSONEqual(t *testing.T, want, got string) {
  1760. t.Helper()
  1761. var wantObj interface{}
  1762. var gotObj interface{}
  1763. if err := json.Unmarshal([]byte(want), &wantObj); err != nil {
  1764. t.Fatalf("failed to unmarshal want JSON: %v", err)
  1765. }
  1766. if err := json.Unmarshal([]byte(got), &gotObj); err != nil {
  1767. t.Fatalf("failed to unmarshal got JSON: %v", err)
  1768. }
  1769. if !reflect.DeepEqual(wantObj, gotObj) {
  1770. t.Fatalf("json not equal\nwant: %s\ngot: %s", want, got)
  1771. }
  1772. }