override_test.go 31 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223
  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 TestApplyParamOverrideSetHeaderKeepOrigin(t *testing.T) {
  970. input := []byte(`{"temperature":0.7}`)
  971. override := map[string]interface{}{
  972. "operations": []interface{}{
  973. map[string]interface{}{
  974. "mode": "set_header",
  975. "path": "X-Feature-Flag",
  976. "value": "new-value",
  977. "keep_origin": true,
  978. },
  979. },
  980. }
  981. ctx := map[string]interface{}{
  982. "header_override": map[string]interface{}{
  983. "X-Feature-Flag": "legacy-value",
  984. },
  985. "header_override_normalized": map[string]interface{}{
  986. "x_feature_flag": "legacy-value",
  987. },
  988. }
  989. _, err := ApplyParamOverride(input, override, ctx)
  990. if err != nil {
  991. t.Fatalf("ApplyParamOverride returned error: %v", err)
  992. }
  993. headers, ok := ctx["header_override"].(map[string]interface{})
  994. if !ok {
  995. t.Fatalf("expected header_override context map")
  996. }
  997. if headers["X-Feature-Flag"] != "legacy-value" {
  998. t.Fatalf("expected keep_origin to preserve old value, got: %v", headers["X-Feature-Flag"])
  999. }
  1000. }
  1001. func TestApplyParamOverrideConditionsObjectShorthand(t *testing.T) {
  1002. input := []byte(`{"temperature":0.7}`)
  1003. override := map[string]interface{}{
  1004. "operations": []interface{}{
  1005. map[string]interface{}{
  1006. "path": "temperature",
  1007. "mode": "set",
  1008. "value": 0.1,
  1009. "logic": "AND",
  1010. "conditions": map[string]interface{}{
  1011. "is_retry": true,
  1012. "last_error.status_code": 400.0,
  1013. },
  1014. },
  1015. },
  1016. }
  1017. ctx := map[string]interface{}{
  1018. "is_retry": true,
  1019. "last_error": map[string]interface{}{
  1020. "status_code": 400.0,
  1021. },
  1022. }
  1023. out, err := ApplyParamOverride(input, override, ctx)
  1024. if err != nil {
  1025. t.Fatalf("ApplyParamOverride returned error: %v", err)
  1026. }
  1027. assertJSONEqual(t, `{"temperature":0.1}`, string(out))
  1028. }
  1029. func TestApplyParamOverrideWithRelayInfoSyncRuntimeHeaders(t *testing.T) {
  1030. info := &RelayInfo{
  1031. ChannelMeta: &ChannelMeta{
  1032. ParamOverride: map[string]interface{}{
  1033. "operations": []interface{}{
  1034. map[string]interface{}{
  1035. "mode": "set_header",
  1036. "path": "X-Injected-By-Param-Override",
  1037. "value": "enabled",
  1038. },
  1039. map[string]interface{}{
  1040. "mode": "delete_header",
  1041. "path": "X-Delete-Me",
  1042. },
  1043. },
  1044. },
  1045. HeadersOverride: map[string]interface{}{
  1046. "X-Delete-Me": "legacy",
  1047. "X-Keep-Me": "keep",
  1048. },
  1049. },
  1050. }
  1051. input := []byte(`{"temperature":0.7}`)
  1052. out, err := ApplyParamOverrideWithRelayInfo(input, info)
  1053. if err != nil {
  1054. t.Fatalf("ApplyParamOverrideWithRelayInfo returned error: %v", err)
  1055. }
  1056. assertJSONEqual(t, `{"temperature":0.7}`, string(out))
  1057. if !info.UseRuntimeHeadersOverride {
  1058. t.Fatalf("expected runtime header override to be enabled")
  1059. }
  1060. if info.RuntimeHeadersOverride["X-Keep-Me"] != "keep" {
  1061. t.Fatalf("expected X-Keep-Me header to be preserved, got: %v", info.RuntimeHeadersOverride["X-Keep-Me"])
  1062. }
  1063. if info.RuntimeHeadersOverride["X-Injected-By-Param-Override"] != "enabled" {
  1064. t.Fatalf("expected X-Injected-By-Param-Override header to be set, got: %v", info.RuntimeHeadersOverride["X-Injected-By-Param-Override"])
  1065. }
  1066. if _, exists := info.RuntimeHeadersOverride["X-Delete-Me"]; exists {
  1067. t.Fatalf("expected X-Delete-Me header to be deleted")
  1068. }
  1069. }
  1070. func TestApplyParamOverrideWithRelayInfoMoveAndCopyHeaders(t *testing.T) {
  1071. info := &RelayInfo{
  1072. ChannelMeta: &ChannelMeta{
  1073. ParamOverride: map[string]interface{}{
  1074. "operations": []interface{}{
  1075. map[string]interface{}{
  1076. "mode": "move_header",
  1077. "from": "X-Legacy-Trace",
  1078. "to": "X-Trace",
  1079. },
  1080. map[string]interface{}{
  1081. "mode": "copy_header",
  1082. "from": "X-Trace",
  1083. "to": "X-Trace-Backup",
  1084. },
  1085. },
  1086. },
  1087. HeadersOverride: map[string]interface{}{
  1088. "X-Legacy-Trace": "trace-123",
  1089. },
  1090. },
  1091. }
  1092. input := []byte(`{"temperature":0.7}`)
  1093. _, err := ApplyParamOverrideWithRelayInfo(input, info)
  1094. if err != nil {
  1095. t.Fatalf("ApplyParamOverrideWithRelayInfo returned error: %v", err)
  1096. }
  1097. if _, exists := info.RuntimeHeadersOverride["X-Legacy-Trace"]; exists {
  1098. t.Fatalf("expected source header to be removed after move")
  1099. }
  1100. if info.RuntimeHeadersOverride["X-Trace"] != "trace-123" {
  1101. t.Fatalf("expected X-Trace to be set, got: %v", info.RuntimeHeadersOverride["X-Trace"])
  1102. }
  1103. if info.RuntimeHeadersOverride["X-Trace-Backup"] != "trace-123" {
  1104. t.Fatalf("expected X-Trace-Backup to be copied, got: %v", info.RuntimeHeadersOverride["X-Trace-Backup"])
  1105. }
  1106. }
  1107. func assertJSONEqual(t *testing.T, want, got string) {
  1108. t.Helper()
  1109. var wantObj interface{}
  1110. var gotObj interface{}
  1111. if err := json.Unmarshal([]byte(want), &wantObj); err != nil {
  1112. t.Fatalf("failed to unmarshal want JSON: %v", err)
  1113. }
  1114. if err := json.Unmarshal([]byte(got), &gotObj); err != nil {
  1115. t.Fatalf("failed to unmarshal got JSON: %v", err)
  1116. }
  1117. if !reflect.DeepEqual(wantObj, gotObj) {
  1118. t.Fatalf("json not equal\nwant: %s\ngot: %s", want, got)
  1119. }
  1120. }