override_test.go 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330
  1. package common
  2. import (
  3. "encoding/json"
  4. "reflect"
  5. "testing"
  6. "github.com/QuantumNous/new-api/types"
  7. )
  8. func TestApplyParamOverrideTrimPrefix(t *testing.T) {
  9. // trim_prefix example:
  10. // {"operations":[{"path":"model","mode":"trim_prefix","value":"openai/"}]}
  11. input := []byte(`{"model":"openai/gpt-4","temperature":0.7}`)
  12. override := map[string]interface{}{
  13. "operations": []interface{}{
  14. map[string]interface{}{
  15. "path": "model",
  16. "mode": "trim_prefix",
  17. "value": "openai/",
  18. },
  19. },
  20. }
  21. out, err := ApplyParamOverride(input, override, nil)
  22. if err != nil {
  23. t.Fatalf("ApplyParamOverride returned error: %v", err)
  24. }
  25. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
  26. }
  27. func TestApplyParamOverrideTrimSuffix(t *testing.T) {
  28. // trim_suffix example:
  29. // {"operations":[{"path":"model","mode":"trim_suffix","value":"-latest"}]}
  30. input := []byte(`{"model":"gpt-4-latest","temperature":0.7}`)
  31. override := map[string]interface{}{
  32. "operations": []interface{}{
  33. map[string]interface{}{
  34. "path": "model",
  35. "mode": "trim_suffix",
  36. "value": "-latest",
  37. },
  38. },
  39. }
  40. out, err := ApplyParamOverride(input, override, nil)
  41. if err != nil {
  42. t.Fatalf("ApplyParamOverride returned error: %v", err)
  43. }
  44. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
  45. }
  46. func TestApplyParamOverrideTrimNoop(t *testing.T) {
  47. // trim_prefix no-op example:
  48. // {"operations":[{"path":"model","mode":"trim_prefix","value":"openai/"}]}
  49. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  50. override := map[string]interface{}{
  51. "operations": []interface{}{
  52. map[string]interface{}{
  53. "path": "model",
  54. "mode": "trim_prefix",
  55. "value": "openai/",
  56. },
  57. },
  58. }
  59. out, err := ApplyParamOverride(input, override, nil)
  60. if err != nil {
  61. t.Fatalf("ApplyParamOverride returned error: %v", err)
  62. }
  63. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
  64. }
  65. func TestApplyParamOverrideTrimRequiresValue(t *testing.T) {
  66. // trim_prefix requires value example:
  67. // {"operations":[{"path":"model","mode":"trim_prefix"}]}
  68. input := []byte(`{"model":"gpt-4"}`)
  69. override := map[string]interface{}{
  70. "operations": []interface{}{
  71. map[string]interface{}{
  72. "path": "model",
  73. "mode": "trim_prefix",
  74. },
  75. },
  76. }
  77. _, err := ApplyParamOverride(input, override, nil)
  78. if err == nil {
  79. t.Fatalf("expected error, got nil")
  80. }
  81. }
  82. func TestApplyParamOverrideReplace(t *testing.T) {
  83. // replace example:
  84. // {"operations":[{"path":"model","mode":"replace","from":"openai/","to":""}]}
  85. input := []byte(`{"model":"openai/gpt-4o-mini","temperature":0.7}`)
  86. override := map[string]interface{}{
  87. "operations": []interface{}{
  88. map[string]interface{}{
  89. "path": "model",
  90. "mode": "replace",
  91. "from": "openai/",
  92. "to": "",
  93. },
  94. },
  95. }
  96. out, err := ApplyParamOverride(input, override, nil)
  97. if err != nil {
  98. t.Fatalf("ApplyParamOverride returned error: %v", err)
  99. }
  100. assertJSONEqual(t, `{"model":"gpt-4o-mini","temperature":0.7}`, string(out))
  101. }
  102. func TestApplyParamOverrideRegexReplace(t *testing.T) {
  103. // regex_replace example:
  104. // {"operations":[{"path":"model","mode":"regex_replace","from":"^gpt-","to":"openai/gpt-"}]}
  105. input := []byte(`{"model":"gpt-4o-mini","temperature":0.7}`)
  106. override := map[string]interface{}{
  107. "operations": []interface{}{
  108. map[string]interface{}{
  109. "path": "model",
  110. "mode": "regex_replace",
  111. "from": "^gpt-",
  112. "to": "openai/gpt-",
  113. },
  114. },
  115. }
  116. out, err := ApplyParamOverride(input, override, nil)
  117. if err != nil {
  118. t.Fatalf("ApplyParamOverride returned error: %v", err)
  119. }
  120. assertJSONEqual(t, `{"model":"openai/gpt-4o-mini","temperature":0.7}`, string(out))
  121. }
  122. func TestApplyParamOverrideReplaceRequiresFrom(t *testing.T) {
  123. // replace requires from example:
  124. // {"operations":[{"path":"model","mode":"replace"}]}
  125. input := []byte(`{"model":"gpt-4"}`)
  126. override := map[string]interface{}{
  127. "operations": []interface{}{
  128. map[string]interface{}{
  129. "path": "model",
  130. "mode": "replace",
  131. },
  132. },
  133. }
  134. _, err := ApplyParamOverride(input, override, nil)
  135. if err == nil {
  136. t.Fatalf("expected error, got nil")
  137. }
  138. }
  139. func TestApplyParamOverrideRegexReplaceRequiresPattern(t *testing.T) {
  140. // regex_replace requires from(pattern) example:
  141. // {"operations":[{"path":"model","mode":"regex_replace"}]}
  142. input := []byte(`{"model":"gpt-4"}`)
  143. override := map[string]interface{}{
  144. "operations": []interface{}{
  145. map[string]interface{}{
  146. "path": "model",
  147. "mode": "regex_replace",
  148. },
  149. },
  150. }
  151. _, err := ApplyParamOverride(input, override, nil)
  152. if err == nil {
  153. t.Fatalf("expected error, got nil")
  154. }
  155. }
  156. func TestApplyParamOverrideDelete(t *testing.T) {
  157. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  158. override := map[string]interface{}{
  159. "operations": []interface{}{
  160. map[string]interface{}{
  161. "path": "temperature",
  162. "mode": "delete",
  163. },
  164. },
  165. }
  166. out, err := ApplyParamOverride(input, override, nil)
  167. if err != nil {
  168. t.Fatalf("ApplyParamOverride returned error: %v", err)
  169. }
  170. var got map[string]interface{}
  171. if err := json.Unmarshal(out, &got); err != nil {
  172. t.Fatalf("failed to unmarshal output JSON: %v", err)
  173. }
  174. if _, exists := got["temperature"]; exists {
  175. t.Fatalf("expected temperature to be deleted")
  176. }
  177. }
  178. func TestApplyParamOverrideSet(t *testing.T) {
  179. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  180. override := map[string]interface{}{
  181. "operations": []interface{}{
  182. map[string]interface{}{
  183. "path": "temperature",
  184. "mode": "set",
  185. "value": 0.1,
  186. },
  187. },
  188. }
  189. out, err := ApplyParamOverride(input, override, nil)
  190. if err != nil {
  191. t.Fatalf("ApplyParamOverride returned error: %v", err)
  192. }
  193. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.1}`, string(out))
  194. }
  195. func TestApplyParamOverrideSetKeepOrigin(t *testing.T) {
  196. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  197. override := map[string]interface{}{
  198. "operations": []interface{}{
  199. map[string]interface{}{
  200. "path": "temperature",
  201. "mode": "set",
  202. "value": 0.1,
  203. "keep_origin": true,
  204. },
  205. },
  206. }
  207. out, err := ApplyParamOverride(input, override, nil)
  208. if err != nil {
  209. t.Fatalf("ApplyParamOverride returned error: %v", err)
  210. }
  211. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
  212. }
  213. func TestApplyParamOverrideMove(t *testing.T) {
  214. input := []byte(`{"model":"gpt-4","meta":{"x":1}}`)
  215. override := map[string]interface{}{
  216. "operations": []interface{}{
  217. map[string]interface{}{
  218. "mode": "move",
  219. "from": "model",
  220. "to": "meta.model",
  221. },
  222. },
  223. }
  224. out, err := ApplyParamOverride(input, override, nil)
  225. if err != nil {
  226. t.Fatalf("ApplyParamOverride returned error: %v", err)
  227. }
  228. assertJSONEqual(t, `{"meta":{"x":1,"model":"gpt-4"}}`, string(out))
  229. }
  230. func TestApplyParamOverrideMoveMissingSource(t *testing.T) {
  231. input := []byte(`{"meta":{"x":1}}`)
  232. override := map[string]interface{}{
  233. "operations": []interface{}{
  234. map[string]interface{}{
  235. "mode": "move",
  236. "from": "model",
  237. "to": "meta.model",
  238. },
  239. },
  240. }
  241. _, err := ApplyParamOverride(input, override, nil)
  242. if err == nil {
  243. t.Fatalf("expected error, got nil")
  244. }
  245. }
  246. func TestApplyParamOverridePrependAppendString(t *testing.T) {
  247. input := []byte(`{"model":"gpt-4"}`)
  248. override := map[string]interface{}{
  249. "operations": []interface{}{
  250. map[string]interface{}{
  251. "path": "model",
  252. "mode": "prepend",
  253. "value": "openai/",
  254. },
  255. map[string]interface{}{
  256. "path": "model",
  257. "mode": "append",
  258. "value": "-latest",
  259. },
  260. },
  261. }
  262. out, err := ApplyParamOverride(input, override, nil)
  263. if err != nil {
  264. t.Fatalf("ApplyParamOverride returned error: %v", err)
  265. }
  266. assertJSONEqual(t, `{"model":"openai/gpt-4-latest"}`, string(out))
  267. }
  268. func TestApplyParamOverridePrependAppendArray(t *testing.T) {
  269. input := []byte(`{"arr":[1,2]}`)
  270. override := map[string]interface{}{
  271. "operations": []interface{}{
  272. map[string]interface{}{
  273. "path": "arr",
  274. "mode": "prepend",
  275. "value": 0,
  276. },
  277. map[string]interface{}{
  278. "path": "arr",
  279. "mode": "append",
  280. "value": []interface{}{3, 4},
  281. },
  282. },
  283. }
  284. out, err := ApplyParamOverride(input, override, nil)
  285. if err != nil {
  286. t.Fatalf("ApplyParamOverride returned error: %v", err)
  287. }
  288. assertJSONEqual(t, `{"arr":[0,1,2,3,4]}`, string(out))
  289. }
  290. func TestApplyParamOverrideAppendObjectMergeKeepOrigin(t *testing.T) {
  291. input := []byte(`{"obj":{"a":1}}`)
  292. override := map[string]interface{}{
  293. "operations": []interface{}{
  294. map[string]interface{}{
  295. "path": "obj",
  296. "mode": "append",
  297. "keep_origin": true,
  298. "value": map[string]interface{}{
  299. "a": 2,
  300. "b": 3,
  301. },
  302. },
  303. },
  304. }
  305. out, err := ApplyParamOverride(input, override, nil)
  306. if err != nil {
  307. t.Fatalf("ApplyParamOverride returned error: %v", err)
  308. }
  309. assertJSONEqual(t, `{"obj":{"a":1,"b":3}}`, string(out))
  310. }
  311. func TestApplyParamOverrideAppendObjectMergeOverride(t *testing.T) {
  312. input := []byte(`{"obj":{"a":1}}`)
  313. override := map[string]interface{}{
  314. "operations": []interface{}{
  315. map[string]interface{}{
  316. "path": "obj",
  317. "mode": "append",
  318. "value": map[string]interface{}{
  319. "a": 2,
  320. "b": 3,
  321. },
  322. },
  323. },
  324. }
  325. out, err := ApplyParamOverride(input, override, nil)
  326. if err != nil {
  327. t.Fatalf("ApplyParamOverride returned error: %v", err)
  328. }
  329. assertJSONEqual(t, `{"obj":{"a":2,"b":3}}`, string(out))
  330. }
  331. func TestApplyParamOverrideConditionORDefault(t *testing.T) {
  332. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  333. override := map[string]interface{}{
  334. "operations": []interface{}{
  335. map[string]interface{}{
  336. "path": "temperature",
  337. "mode": "set",
  338. "value": 0.1,
  339. "conditions": []interface{}{
  340. map[string]interface{}{
  341. "path": "model",
  342. "mode": "prefix",
  343. "value": "gpt",
  344. },
  345. map[string]interface{}{
  346. "path": "model",
  347. "mode": "prefix",
  348. "value": "claude",
  349. },
  350. },
  351. },
  352. },
  353. }
  354. out, err := ApplyParamOverride(input, override, nil)
  355. if err != nil {
  356. t.Fatalf("ApplyParamOverride returned error: %v", err)
  357. }
  358. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.1}`, string(out))
  359. }
  360. func TestApplyParamOverrideConditionAND(t *testing.T) {
  361. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  362. override := map[string]interface{}{
  363. "operations": []interface{}{
  364. map[string]interface{}{
  365. "path": "temperature",
  366. "mode": "set",
  367. "value": 0.1,
  368. "logic": "AND",
  369. "conditions": []interface{}{
  370. map[string]interface{}{
  371. "path": "model",
  372. "mode": "prefix",
  373. "value": "gpt",
  374. },
  375. map[string]interface{}{
  376. "path": "temperature",
  377. "mode": "gt",
  378. "value": 0.5,
  379. },
  380. },
  381. },
  382. },
  383. }
  384. out, err := ApplyParamOverride(input, override, nil)
  385. if err != nil {
  386. t.Fatalf("ApplyParamOverride returned error: %v", err)
  387. }
  388. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.1}`, string(out))
  389. }
  390. func TestApplyParamOverrideConditionInvert(t *testing.T) {
  391. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  392. override := map[string]interface{}{
  393. "operations": []interface{}{
  394. map[string]interface{}{
  395. "path": "temperature",
  396. "mode": "set",
  397. "value": 0.1,
  398. "conditions": []interface{}{
  399. map[string]interface{}{
  400. "path": "model",
  401. "mode": "prefix",
  402. "value": "gpt",
  403. "invert": true,
  404. },
  405. },
  406. },
  407. },
  408. }
  409. out, err := ApplyParamOverride(input, override, nil)
  410. if err != nil {
  411. t.Fatalf("ApplyParamOverride returned error: %v", err)
  412. }
  413. assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
  414. }
  415. func TestApplyParamOverrideConditionPassMissingKey(t *testing.T) {
  416. input := []byte(`{"temperature":0.7}`)
  417. override := map[string]interface{}{
  418. "operations": []interface{}{
  419. map[string]interface{}{
  420. "path": "temperature",
  421. "mode": "set",
  422. "value": 0.1,
  423. "conditions": []interface{}{
  424. map[string]interface{}{
  425. "path": "model",
  426. "mode": "prefix",
  427. "value": "gpt",
  428. "pass_missing_key": true,
  429. },
  430. },
  431. },
  432. },
  433. }
  434. out, err := ApplyParamOverride(input, override, nil)
  435. if err != nil {
  436. t.Fatalf("ApplyParamOverride returned error: %v", err)
  437. }
  438. assertJSONEqual(t, `{"temperature":0.1}`, string(out))
  439. }
  440. func TestApplyParamOverrideConditionFromContext(t *testing.T) {
  441. input := []byte(`{"temperature":0.7}`)
  442. override := map[string]interface{}{
  443. "operations": []interface{}{
  444. map[string]interface{}{
  445. "path": "temperature",
  446. "mode": "set",
  447. "value": 0.1,
  448. "conditions": []interface{}{
  449. map[string]interface{}{
  450. "path": "model",
  451. "mode": "prefix",
  452. "value": "gpt",
  453. },
  454. },
  455. },
  456. },
  457. }
  458. ctx := map[string]interface{}{
  459. "model": "gpt-4",
  460. }
  461. out, err := ApplyParamOverride(input, override, ctx)
  462. if err != nil {
  463. t.Fatalf("ApplyParamOverride returned error: %v", err)
  464. }
  465. assertJSONEqual(t, `{"temperature":0.1}`, string(out))
  466. }
  467. func TestApplyParamOverrideNegativeIndexPath(t *testing.T) {
  468. input := []byte(`{"arr":[{"model":"a"},{"model":"b"}]}`)
  469. override := map[string]interface{}{
  470. "operations": []interface{}{
  471. map[string]interface{}{
  472. "path": "arr.-1.model",
  473. "mode": "set",
  474. "value": "c",
  475. },
  476. },
  477. }
  478. out, err := ApplyParamOverride(input, override, nil)
  479. if err != nil {
  480. t.Fatalf("ApplyParamOverride returned error: %v", err)
  481. }
  482. assertJSONEqual(t, `{"arr":[{"model":"a"},{"model":"c"}]}`, string(out))
  483. }
  484. func TestApplyParamOverrideRegexReplaceInvalidPattern(t *testing.T) {
  485. // regex_replace invalid pattern example:
  486. // {"operations":[{"path":"model","mode":"regex_replace","from":"(","to":"x"}]}
  487. input := []byte(`{"model":"gpt-4"}`)
  488. override := map[string]interface{}{
  489. "operations": []interface{}{
  490. map[string]interface{}{
  491. "path": "model",
  492. "mode": "regex_replace",
  493. "from": "(",
  494. "to": "x",
  495. },
  496. },
  497. }
  498. _, err := ApplyParamOverride(input, override, nil)
  499. if err == nil {
  500. t.Fatalf("expected error, got nil")
  501. }
  502. }
  503. func TestApplyParamOverrideCopy(t *testing.T) {
  504. // copy example:
  505. // {"operations":[{"mode":"copy","from":"model","to":"original_model"}]}
  506. input := []byte(`{"model":"gpt-4","temperature":0.7}`)
  507. override := map[string]interface{}{
  508. "operations": []interface{}{
  509. map[string]interface{}{
  510. "mode": "copy",
  511. "from": "model",
  512. "to": "original_model",
  513. },
  514. },
  515. }
  516. out, err := ApplyParamOverride(input, override, nil)
  517. if err != nil {
  518. t.Fatalf("ApplyParamOverride returned error: %v", err)
  519. }
  520. assertJSONEqual(t, `{"model":"gpt-4","original_model":"gpt-4","temperature":0.7}`, string(out))
  521. }
  522. func TestApplyParamOverrideCopyMissingSource(t *testing.T) {
  523. // copy missing source example:
  524. // {"operations":[{"mode":"copy","from":"model","to":"original_model"}]}
  525. input := []byte(`{"temperature":0.7}`)
  526. override := map[string]interface{}{
  527. "operations": []interface{}{
  528. map[string]interface{}{
  529. "mode": "copy",
  530. "from": "model",
  531. "to": "original_model",
  532. },
  533. },
  534. }
  535. _, err := ApplyParamOverride(input, override, nil)
  536. if err == nil {
  537. t.Fatalf("expected error, got nil")
  538. }
  539. }
  540. func TestApplyParamOverrideCopyRequiresFromTo(t *testing.T) {
  541. // copy requires from/to example:
  542. // {"operations":[{"mode":"copy"}]}
  543. input := []byte(`{"model":"gpt-4"}`)
  544. override := map[string]interface{}{
  545. "operations": []interface{}{
  546. map[string]interface{}{
  547. "mode": "copy",
  548. },
  549. },
  550. }
  551. _, err := ApplyParamOverride(input, override, nil)
  552. if err == nil {
  553. t.Fatalf("expected error, got nil")
  554. }
  555. }
  556. func TestApplyParamOverrideEnsurePrefix(t *testing.T) {
  557. // ensure_prefix example:
  558. // {"operations":[{"path":"model","mode":"ensure_prefix","value":"openai/"}]}
  559. input := []byte(`{"model":"gpt-4"}`)
  560. override := map[string]interface{}{
  561. "operations": []interface{}{
  562. map[string]interface{}{
  563. "path": "model",
  564. "mode": "ensure_prefix",
  565. "value": "openai/",
  566. },
  567. },
  568. }
  569. out, err := ApplyParamOverride(input, override, nil)
  570. if err != nil {
  571. t.Fatalf("ApplyParamOverride returned error: %v", err)
  572. }
  573. assertJSONEqual(t, `{"model":"openai/gpt-4"}`, string(out))
  574. }
  575. func TestApplyParamOverrideEnsurePrefixNoop(t *testing.T) {
  576. // ensure_prefix no-op example:
  577. // {"operations":[{"path":"model","mode":"ensure_prefix","value":"openai/"}]}
  578. input := []byte(`{"model":"openai/gpt-4"}`)
  579. override := map[string]interface{}{
  580. "operations": []interface{}{
  581. map[string]interface{}{
  582. "path": "model",
  583. "mode": "ensure_prefix",
  584. "value": "openai/",
  585. },
  586. },
  587. }
  588. out, err := ApplyParamOverride(input, override, nil)
  589. if err != nil {
  590. t.Fatalf("ApplyParamOverride returned error: %v", err)
  591. }
  592. assertJSONEqual(t, `{"model":"openai/gpt-4"}`, string(out))
  593. }
  594. func TestApplyParamOverrideEnsureSuffix(t *testing.T) {
  595. // ensure_suffix example:
  596. // {"operations":[{"path":"model","mode":"ensure_suffix","value":"-latest"}]}
  597. input := []byte(`{"model":"gpt-4"}`)
  598. override := map[string]interface{}{
  599. "operations": []interface{}{
  600. map[string]interface{}{
  601. "path": "model",
  602. "mode": "ensure_suffix",
  603. "value": "-latest",
  604. },
  605. },
  606. }
  607. out, err := ApplyParamOverride(input, override, nil)
  608. if err != nil {
  609. t.Fatalf("ApplyParamOverride returned error: %v", err)
  610. }
  611. assertJSONEqual(t, `{"model":"gpt-4-latest"}`, string(out))
  612. }
  613. func TestApplyParamOverrideEnsureSuffixNoop(t *testing.T) {
  614. // ensure_suffix no-op example:
  615. // {"operations":[{"path":"model","mode":"ensure_suffix","value":"-latest"}]}
  616. input := []byte(`{"model":"gpt-4-latest"}`)
  617. override := map[string]interface{}{
  618. "operations": []interface{}{
  619. map[string]interface{}{
  620. "path": "model",
  621. "mode": "ensure_suffix",
  622. "value": "-latest",
  623. },
  624. },
  625. }
  626. out, err := ApplyParamOverride(input, override, nil)
  627. if err != nil {
  628. t.Fatalf("ApplyParamOverride returned error: %v", err)
  629. }
  630. assertJSONEqual(t, `{"model":"gpt-4-latest"}`, string(out))
  631. }
  632. func TestApplyParamOverrideEnsureRequiresValue(t *testing.T) {
  633. // ensure_prefix requires value example:
  634. // {"operations":[{"path":"model","mode":"ensure_prefix"}]}
  635. input := []byte(`{"model":"gpt-4"}`)
  636. override := map[string]interface{}{
  637. "operations": []interface{}{
  638. map[string]interface{}{
  639. "path": "model",
  640. "mode": "ensure_prefix",
  641. },
  642. },
  643. }
  644. _, err := ApplyParamOverride(input, override, nil)
  645. if err == nil {
  646. t.Fatalf("expected error, got nil")
  647. }
  648. }
  649. func TestApplyParamOverrideTrimSpace(t *testing.T) {
  650. // trim_space example:
  651. // {"operations":[{"path":"model","mode":"trim_space"}]}
  652. input := []byte("{\"model\":\" gpt-4 \\n\"}")
  653. override := map[string]interface{}{
  654. "operations": []interface{}{
  655. map[string]interface{}{
  656. "path": "model",
  657. "mode": "trim_space",
  658. },
  659. },
  660. }
  661. out, err := ApplyParamOverride(input, override, nil)
  662. if err != nil {
  663. t.Fatalf("ApplyParamOverride returned error: %v", err)
  664. }
  665. assertJSONEqual(t, `{"model":"gpt-4"}`, string(out))
  666. }
  667. func TestApplyParamOverrideToLower(t *testing.T) {
  668. // to_lower example:
  669. // {"operations":[{"path":"model","mode":"to_lower"}]}
  670. input := []byte(`{"model":"GPT-4"}`)
  671. override := map[string]interface{}{
  672. "operations": []interface{}{
  673. map[string]interface{}{
  674. "path": "model",
  675. "mode": "to_lower",
  676. },
  677. },
  678. }
  679. out, err := ApplyParamOverride(input, override, nil)
  680. if err != nil {
  681. t.Fatalf("ApplyParamOverride returned error: %v", err)
  682. }
  683. assertJSONEqual(t, `{"model":"gpt-4"}`, string(out))
  684. }
  685. func TestApplyParamOverrideToUpper(t *testing.T) {
  686. // to_upper example:
  687. // {"operations":[{"path":"model","mode":"to_upper"}]}
  688. input := []byte(`{"model":"gpt-4"}`)
  689. override := map[string]interface{}{
  690. "operations": []interface{}{
  691. map[string]interface{}{
  692. "path": "model",
  693. "mode": "to_upper",
  694. },
  695. },
  696. }
  697. out, err := ApplyParamOverride(input, override, nil)
  698. if err != nil {
  699. t.Fatalf("ApplyParamOverride returned error: %v", err)
  700. }
  701. assertJSONEqual(t, `{"model":"GPT-4"}`, string(out))
  702. }
  703. func TestApplyParamOverrideReturnError(t *testing.T) {
  704. input := []byte(`{"model":"gemini-2.5-pro"}`)
  705. override := map[string]interface{}{
  706. "operations": []interface{}{
  707. map[string]interface{}{
  708. "mode": "return_error",
  709. "value": map[string]interface{}{
  710. "message": "forced bad request by param override",
  711. "status_code": 422,
  712. "code": "forced_bad_request",
  713. "type": "invalid_request_error",
  714. "skip_retry": true,
  715. },
  716. "conditions": []interface{}{
  717. map[string]interface{}{
  718. "path": "retry.is_retry",
  719. "mode": "full",
  720. "value": true,
  721. },
  722. },
  723. },
  724. },
  725. }
  726. ctx := map[string]interface{}{
  727. "retry": map[string]interface{}{
  728. "index": 1,
  729. "is_retry": true,
  730. },
  731. }
  732. _, err := ApplyParamOverride(input, override, ctx)
  733. if err == nil {
  734. t.Fatalf("expected error, got nil")
  735. }
  736. returnErr, ok := AsParamOverrideReturnError(err)
  737. if !ok {
  738. t.Fatalf("expected ParamOverrideReturnError, got %T: %v", err, err)
  739. }
  740. if returnErr.StatusCode != 422 {
  741. t.Fatalf("expected status 422, got %d", returnErr.StatusCode)
  742. }
  743. if returnErr.Code != "forced_bad_request" {
  744. t.Fatalf("expected code forced_bad_request, got %s", returnErr.Code)
  745. }
  746. if !returnErr.SkipRetry {
  747. t.Fatalf("expected skip_retry true")
  748. }
  749. }
  750. func TestApplyParamOverridePruneObjectsByTypeString(t *testing.T) {
  751. input := []byte(`{
  752. "messages":[
  753. {"role":"assistant","content":[
  754. {"type":"output_text","text":"a"},
  755. {"type":"redacted_thinking","text":"secret"},
  756. {"type":"tool_call","name":"tool_a"}
  757. ]},
  758. {"role":"assistant","content":[
  759. {"type":"output_text","text":"b"},
  760. {"type":"wrapper","parts":[
  761. {"type":"redacted_thinking","text":"secret2"},
  762. {"type":"output_text","text":"c"}
  763. ]}
  764. ]}
  765. ]
  766. }`)
  767. override := map[string]interface{}{
  768. "operations": []interface{}{
  769. map[string]interface{}{
  770. "mode": "prune_objects",
  771. "value": "redacted_thinking",
  772. },
  773. },
  774. }
  775. out, err := ApplyParamOverride(input, override, nil)
  776. if err != nil {
  777. t.Fatalf("ApplyParamOverride returned error: %v", err)
  778. }
  779. assertJSONEqual(t, `{
  780. "messages":[
  781. {"role":"assistant","content":[
  782. {"type":"output_text","text":"a"},
  783. {"type":"tool_call","name":"tool_a"}
  784. ]},
  785. {"role":"assistant","content":[
  786. {"type":"output_text","text":"b"},
  787. {"type":"wrapper","parts":[
  788. {"type":"output_text","text":"c"}
  789. ]}
  790. ]}
  791. ]
  792. }`, string(out))
  793. }
  794. func TestApplyParamOverridePruneObjectsWhereAndPath(t *testing.T) {
  795. input := []byte(`{
  796. "a":{"items":[{"type":"redacted_thinking","id":1},{"type":"output_text","id":2}]},
  797. "b":{"items":[{"type":"redacted_thinking","id":3},{"type":"output_text","id":4}]}
  798. }`)
  799. override := map[string]interface{}{
  800. "operations": []interface{}{
  801. map[string]interface{}{
  802. "path": "a",
  803. "mode": "prune_objects",
  804. "value": map[string]interface{}{
  805. "where": map[string]interface{}{
  806. "type": "redacted_thinking",
  807. },
  808. },
  809. },
  810. },
  811. }
  812. out, err := ApplyParamOverride(input, override, nil)
  813. if err != nil {
  814. t.Fatalf("ApplyParamOverride returned error: %v", err)
  815. }
  816. assertJSONEqual(t, `{
  817. "a":{"items":[{"type":"output_text","id":2}]},
  818. "b":{"items":[{"type":"redacted_thinking","id":3},{"type":"output_text","id":4}]}
  819. }`, string(out))
  820. }
  821. func TestApplyParamOverrideNormalizeThinkingSignatureUnsupported(t *testing.T) {
  822. input := []byte(`{"items":[{"type":"redacted_thinking"}]}`)
  823. override := map[string]interface{}{
  824. "operations": []interface{}{
  825. map[string]interface{}{
  826. "mode": "normalize_thinking_signature",
  827. },
  828. },
  829. }
  830. _, err := ApplyParamOverride(input, override, nil)
  831. if err == nil {
  832. t.Fatalf("expected error, got nil")
  833. }
  834. }
  835. func TestApplyParamOverrideConditionFromRetryAndLastErrorContext(t *testing.T) {
  836. info := &RelayInfo{
  837. RetryIndex: 1,
  838. LastError: types.WithOpenAIError(types.OpenAIError{
  839. Message: "invalid thinking signature",
  840. Type: "invalid_request_error",
  841. Code: "bad_thought_signature",
  842. }, 400),
  843. }
  844. ctx := BuildParamOverrideContext(info)
  845. input := []byte(`{"temperature":0.7}`)
  846. override := map[string]interface{}{
  847. "operations": []interface{}{
  848. map[string]interface{}{
  849. "path": "temperature",
  850. "mode": "set",
  851. "value": 0.1,
  852. "logic": "AND",
  853. "conditions": []interface{}{
  854. map[string]interface{}{
  855. "path": "is_retry",
  856. "mode": "full",
  857. "value": true,
  858. },
  859. map[string]interface{}{
  860. "path": "last_error.code",
  861. "mode": "contains",
  862. "value": "thought_signature",
  863. },
  864. },
  865. },
  866. },
  867. }
  868. out, err := ApplyParamOverride(input, override, ctx)
  869. if err != nil {
  870. t.Fatalf("ApplyParamOverride returned error: %v", err)
  871. }
  872. assertJSONEqual(t, `{"temperature":0.1}`, string(out))
  873. }
  874. func TestApplyParamOverrideConditionFromRequestHeaders(t *testing.T) {
  875. input := []byte(`{"temperature":0.7}`)
  876. override := map[string]interface{}{
  877. "operations": []interface{}{
  878. map[string]interface{}{
  879. "path": "temperature",
  880. "mode": "set",
  881. "value": 0.1,
  882. "conditions": []interface{}{
  883. map[string]interface{}{
  884. "path": "request_headers.authorization",
  885. "mode": "contains",
  886. "value": "Bearer ",
  887. },
  888. },
  889. },
  890. },
  891. }
  892. ctx := map[string]interface{}{
  893. "request_headers": map[string]interface{}{
  894. "authorization": "Bearer token-123",
  895. },
  896. }
  897. out, err := ApplyParamOverride(input, override, ctx)
  898. if err != nil {
  899. t.Fatalf("ApplyParamOverride returned error: %v", err)
  900. }
  901. assertJSONEqual(t, `{"temperature":0.1}`, string(out))
  902. }
  903. func TestApplyParamOverrideSetHeaderAndUseInLaterCondition(t *testing.T) {
  904. input := []byte(`{"temperature":0.7}`)
  905. override := map[string]interface{}{
  906. "operations": []interface{}{
  907. map[string]interface{}{
  908. "mode": "set_header",
  909. "path": "X-Debug-Mode",
  910. "value": "enabled",
  911. },
  912. map[string]interface{}{
  913. "path": "temperature",
  914. "mode": "set",
  915. "value": 0.1,
  916. "conditions": []interface{}{
  917. map[string]interface{}{
  918. "path": "header_override_normalized.x_debug_mode",
  919. "mode": "full",
  920. "value": "enabled",
  921. },
  922. },
  923. },
  924. },
  925. }
  926. out, err := ApplyParamOverride(input, override, nil)
  927. if err != nil {
  928. t.Fatalf("ApplyParamOverride returned error: %v", err)
  929. }
  930. assertJSONEqual(t, `{"temperature":0.1}`, string(out))
  931. }
  932. func TestApplyParamOverrideCopyHeaderFromRequestHeaders(t *testing.T) {
  933. input := []byte(`{"temperature":0.7}`)
  934. override := map[string]interface{}{
  935. "operations": []interface{}{
  936. map[string]interface{}{
  937. "mode": "copy_header",
  938. "from": "Authorization",
  939. "to": "X-Upstream-Auth",
  940. },
  941. map[string]interface{}{
  942. "path": "temperature",
  943. "mode": "set",
  944. "value": 0.1,
  945. "conditions": []interface{}{
  946. map[string]interface{}{
  947. "path": "header_override_normalized.x_upstream_auth",
  948. "mode": "contains",
  949. "value": "Bearer ",
  950. },
  951. },
  952. },
  953. },
  954. }
  955. ctx := map[string]interface{}{
  956. "request_headers_raw": map[string]interface{}{
  957. "Authorization": "Bearer token-123",
  958. },
  959. "request_headers": map[string]interface{}{
  960. "authorization": "Bearer token-123",
  961. },
  962. }
  963. out, err := ApplyParamOverride(input, override, ctx)
  964. if err != nil {
  965. t.Fatalf("ApplyParamOverride returned error: %v", err)
  966. }
  967. assertJSONEqual(t, `{"temperature":0.1}`, string(out))
  968. }
  969. func TestApplyParamOverrideSyncFieldsHeaderToJSON(t *testing.T) {
  970. input := []byte(`{"model":"gpt-4"}`)
  971. override := map[string]interface{}{
  972. "operations": []interface{}{
  973. map[string]interface{}{
  974. "mode": "sync_fields",
  975. "from": "header:session_id",
  976. "to": "json:prompt_cache_key",
  977. },
  978. },
  979. }
  980. ctx := map[string]interface{}{
  981. "request_headers_raw": map[string]interface{}{
  982. "session_id": "sess-123",
  983. },
  984. "request_headers": map[string]interface{}{
  985. "session_id": "sess-123",
  986. },
  987. }
  988. out, err := ApplyParamOverride(input, override, ctx)
  989. if err != nil {
  990. t.Fatalf("ApplyParamOverride returned error: %v", err)
  991. }
  992. assertJSONEqual(t, `{"model":"gpt-4","prompt_cache_key":"sess-123"}`, string(out))
  993. }
  994. func TestApplyParamOverrideSyncFieldsJSONToHeader(t *testing.T) {
  995. input := []byte(`{"model":"gpt-4","prompt_cache_key":"cache-abc"}`)
  996. override := map[string]interface{}{
  997. "operations": []interface{}{
  998. map[string]interface{}{
  999. "mode": "sync_fields",
  1000. "from": "header:session_id",
  1001. "to": "json:prompt_cache_key",
  1002. },
  1003. },
  1004. }
  1005. ctx := map[string]interface{}{}
  1006. out, err := ApplyParamOverride(input, override, ctx)
  1007. if err != nil {
  1008. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1009. }
  1010. assertJSONEqual(t, `{"model":"gpt-4","prompt_cache_key":"cache-abc"}`, string(out))
  1011. headers, ok := ctx["header_override"].(map[string]interface{})
  1012. if !ok {
  1013. t.Fatalf("expected header_override context map")
  1014. }
  1015. if headers["session_id"] != "cache-abc" {
  1016. t.Fatalf("expected session_id to be synced from prompt_cache_key, got: %v", headers["session_id"])
  1017. }
  1018. }
  1019. func TestApplyParamOverrideSyncFieldsNoChangeWhenBothExist(t *testing.T) {
  1020. input := []byte(`{"model":"gpt-4","prompt_cache_key":"cache-body"}`)
  1021. override := map[string]interface{}{
  1022. "operations": []interface{}{
  1023. map[string]interface{}{
  1024. "mode": "sync_fields",
  1025. "from": "header:session_id",
  1026. "to": "json:prompt_cache_key",
  1027. },
  1028. },
  1029. }
  1030. ctx := map[string]interface{}{
  1031. "request_headers_raw": map[string]interface{}{
  1032. "session_id": "cache-header",
  1033. },
  1034. "request_headers": map[string]interface{}{
  1035. "session_id": "cache-header",
  1036. },
  1037. }
  1038. out, err := ApplyParamOverride(input, override, ctx)
  1039. if err != nil {
  1040. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1041. }
  1042. assertJSONEqual(t, `{"model":"gpt-4","prompt_cache_key":"cache-body"}`, string(out))
  1043. headers, _ := ctx["header_override"].(map[string]interface{})
  1044. if headers != nil {
  1045. if _, exists := headers["session_id"]; exists {
  1046. t.Fatalf("expected no override when both sides already have value")
  1047. }
  1048. }
  1049. }
  1050. func TestApplyParamOverrideSyncFieldsInvalidTarget(t *testing.T) {
  1051. input := []byte(`{"model":"gpt-4"}`)
  1052. override := map[string]interface{}{
  1053. "operations": []interface{}{
  1054. map[string]interface{}{
  1055. "mode": "sync_fields",
  1056. "from": "foo:session_id",
  1057. "to": "json:prompt_cache_key",
  1058. },
  1059. },
  1060. }
  1061. _, err := ApplyParamOverride(input, override, nil)
  1062. if err == nil {
  1063. t.Fatalf("expected error, got nil")
  1064. }
  1065. }
  1066. func TestApplyParamOverrideSetHeaderKeepOrigin(t *testing.T) {
  1067. input := []byte(`{"temperature":0.7}`)
  1068. override := map[string]interface{}{
  1069. "operations": []interface{}{
  1070. map[string]interface{}{
  1071. "mode": "set_header",
  1072. "path": "X-Feature-Flag",
  1073. "value": "new-value",
  1074. "keep_origin": true,
  1075. },
  1076. },
  1077. }
  1078. ctx := map[string]interface{}{
  1079. "header_override": map[string]interface{}{
  1080. "X-Feature-Flag": "legacy-value",
  1081. },
  1082. "header_override_normalized": map[string]interface{}{
  1083. "x_feature_flag": "legacy-value",
  1084. },
  1085. }
  1086. _, err := ApplyParamOverride(input, override, ctx)
  1087. if err != nil {
  1088. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1089. }
  1090. headers, ok := ctx["header_override"].(map[string]interface{})
  1091. if !ok {
  1092. t.Fatalf("expected header_override context map")
  1093. }
  1094. if headers["X-Feature-Flag"] != "legacy-value" {
  1095. t.Fatalf("expected keep_origin to preserve old value, got: %v", headers["X-Feature-Flag"])
  1096. }
  1097. }
  1098. func TestApplyParamOverrideConditionsObjectShorthand(t *testing.T) {
  1099. input := []byte(`{"temperature":0.7}`)
  1100. override := map[string]interface{}{
  1101. "operations": []interface{}{
  1102. map[string]interface{}{
  1103. "path": "temperature",
  1104. "mode": "set",
  1105. "value": 0.1,
  1106. "logic": "AND",
  1107. "conditions": map[string]interface{}{
  1108. "is_retry": true,
  1109. "last_error.status_code": 400.0,
  1110. },
  1111. },
  1112. },
  1113. }
  1114. ctx := map[string]interface{}{
  1115. "is_retry": true,
  1116. "last_error": map[string]interface{}{
  1117. "status_code": 400.0,
  1118. },
  1119. }
  1120. out, err := ApplyParamOverride(input, override, ctx)
  1121. if err != nil {
  1122. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1123. }
  1124. assertJSONEqual(t, `{"temperature":0.1}`, string(out))
  1125. }
  1126. func TestApplyParamOverrideWithRelayInfoSyncRuntimeHeaders(t *testing.T) {
  1127. info := &RelayInfo{
  1128. ChannelMeta: &ChannelMeta{
  1129. ParamOverride: map[string]interface{}{
  1130. "operations": []interface{}{
  1131. map[string]interface{}{
  1132. "mode": "set_header",
  1133. "path": "X-Injected-By-Param-Override",
  1134. "value": "enabled",
  1135. },
  1136. map[string]interface{}{
  1137. "mode": "delete_header",
  1138. "path": "X-Delete-Me",
  1139. },
  1140. },
  1141. },
  1142. HeadersOverride: map[string]interface{}{
  1143. "X-Delete-Me": "legacy",
  1144. "X-Keep-Me": "keep",
  1145. },
  1146. },
  1147. }
  1148. input := []byte(`{"temperature":0.7}`)
  1149. out, err := ApplyParamOverrideWithRelayInfo(input, info)
  1150. if err != nil {
  1151. t.Fatalf("ApplyParamOverrideWithRelayInfo returned error: %v", err)
  1152. }
  1153. assertJSONEqual(t, `{"temperature":0.7}`, string(out))
  1154. if !info.UseRuntimeHeadersOverride {
  1155. t.Fatalf("expected runtime header override to be enabled")
  1156. }
  1157. if info.RuntimeHeadersOverride["X-Keep-Me"] != "keep" {
  1158. t.Fatalf("expected X-Keep-Me header to be preserved, got: %v", info.RuntimeHeadersOverride["X-Keep-Me"])
  1159. }
  1160. if info.RuntimeHeadersOverride["X-Injected-By-Param-Override"] != "enabled" {
  1161. t.Fatalf("expected X-Injected-By-Param-Override header to be set, got: %v", info.RuntimeHeadersOverride["X-Injected-By-Param-Override"])
  1162. }
  1163. if _, exists := info.RuntimeHeadersOverride["X-Delete-Me"]; exists {
  1164. t.Fatalf("expected X-Delete-Me header to be deleted")
  1165. }
  1166. }
  1167. func TestApplyParamOverrideWithRelayInfoMoveAndCopyHeaders(t *testing.T) {
  1168. info := &RelayInfo{
  1169. ChannelMeta: &ChannelMeta{
  1170. ParamOverride: map[string]interface{}{
  1171. "operations": []interface{}{
  1172. map[string]interface{}{
  1173. "mode": "move_header",
  1174. "from": "X-Legacy-Trace",
  1175. "to": "X-Trace",
  1176. },
  1177. map[string]interface{}{
  1178. "mode": "copy_header",
  1179. "from": "X-Trace",
  1180. "to": "X-Trace-Backup",
  1181. },
  1182. },
  1183. },
  1184. HeadersOverride: map[string]interface{}{
  1185. "X-Legacy-Trace": "trace-123",
  1186. },
  1187. },
  1188. }
  1189. input := []byte(`{"temperature":0.7}`)
  1190. _, err := ApplyParamOverrideWithRelayInfo(input, info)
  1191. if err != nil {
  1192. t.Fatalf("ApplyParamOverrideWithRelayInfo returned error: %v", err)
  1193. }
  1194. if _, exists := info.RuntimeHeadersOverride["X-Legacy-Trace"]; exists {
  1195. t.Fatalf("expected source header to be removed after move")
  1196. }
  1197. if info.RuntimeHeadersOverride["X-Trace"] != "trace-123" {
  1198. t.Fatalf("expected X-Trace to be set, got: %v", info.RuntimeHeadersOverride["X-Trace"])
  1199. }
  1200. if info.RuntimeHeadersOverride["X-Trace-Backup"] != "trace-123" {
  1201. t.Fatalf("expected X-Trace-Backup to be copied, got: %v", info.RuntimeHeadersOverride["X-Trace-Backup"])
  1202. }
  1203. }
  1204. func assertJSONEqual(t *testing.T, want, got string) {
  1205. t.Helper()
  1206. var wantObj interface{}
  1207. var gotObj interface{}
  1208. if err := json.Unmarshal([]byte(want), &wantObj); err != nil {
  1209. t.Fatalf("failed to unmarshal want JSON: %v", err)
  1210. }
  1211. if err := json.Unmarshal([]byte(got), &gotObj); err != nil {
  1212. t.Fatalf("failed to unmarshal got JSON: %v", err)
  1213. }
  1214. if !reflect.DeepEqual(wantObj, gotObj) {
  1215. t.Fatalf("json not equal\nwant: %s\ngot: %s", want, got)
  1216. }
  1217. }