request_test.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. package openai
  2. import "testing"
  3. func TestIsCodexCLIRequest(t *testing.T) {
  4. tests := []struct {
  5. name string
  6. ua string
  7. want bool
  8. }{
  9. {name: "codex_cli_rs 前缀", ua: "codex_cli_rs/0.1.0", want: true},
  10. {name: "codex_vscode 前缀", ua: "codex_vscode/1.2.3", want: true},
  11. {name: "大小写混合", ua: "Codex_CLI_Rs/0.1.0", want: true},
  12. {name: "复合 UA 包含 codex", ua: "Mozilla/5.0 codex_cli_rs/0.1.0", want: true},
  13. {name: "空白包裹", ua: " codex_vscode/1.2.3 ", want: true},
  14. {name: "非 codex", ua: "curl/8.0.1", want: false},
  15. {name: "空字符串", ua: "", want: false},
  16. }
  17. for _, tt := range tests {
  18. t.Run(tt.name, func(t *testing.T) {
  19. got := IsCodexCLIRequest(tt.ua)
  20. if got != tt.want {
  21. t.Fatalf("IsCodexCLIRequest(%q) = %v, want %v", tt.ua, got, tt.want)
  22. }
  23. })
  24. }
  25. }
  26. func TestCodexUATrailerName(t *testing.T) {
  27. tests := []struct {
  28. name string
  29. ua string
  30. want string
  31. }{
  32. // 典型 cccc override 场景:前缀改写但尾部保留真实 clientInfo.name
  33. {name: "cccc override → codex-tui", ua: "cccc/0.141.0 (mac os 14.6.1; arm64) apple_terminal/453 (codex-tui; 0.141.0)", want: "codex-tui"},
  34. {name: "cccc override Ubuntu", ua: "cccc/0.139.0 (ubuntu 22.4.0; x86_64) screen (codex-tui; 0.139.0)", want: "codex-tui"},
  35. // 官方客户端自报名称
  36. {name: "Codex Desktop 自报(小写后)", ua: "codex desktop/0.142.0 (mac os 26.0.1; arm64) unknown (codex desktop; 26.616.71553)", want: "codex desktop"},
  37. {name: "codex-tui 自报", ua: "codex-tui/0.141.0 (mac os 15.5.0; arm64) ghostty/1.3.1 (codex-tui; 0.141.0)", want: "codex-tui"},
  38. {name: "codex_exec 自报", ua: "codex_exec/0.141.0 (mac os 14.7.3; arm64) apple_terminal (codex_exec; 0.141.0)", want: "codex_exec"},
  39. // 无括号/无尾部组
  40. {name: "curl 无括号", ua: "curl/8.0.1", want: ""},
  41. {name: "空字符串", ua: "", want: ""},
  42. // 非 codex 尾部
  43. {name: "非 codex 尾部不影响", ua: "evil/0.1.0 (linux; x86_64) bash (evil; 0.1.0)", want: "evil"},
  44. }
  45. for _, tt := range tests {
  46. t.Run(tt.name, func(t *testing.T) {
  47. got := codexUATrailerName(tt.ua)
  48. if got != tt.want {
  49. t.Fatalf("codexUATrailerName(%q) = %q, want %q", tt.ua, got, tt.want)
  50. }
  51. })
  52. }
  53. }
  54. func TestIsCodexOfficialClientRequest(t *testing.T) {
  55. tests := []struct {
  56. name string
  57. ua string
  58. want bool
  59. }{
  60. {name: "codex_cli_rs 前缀", ua: "codex_cli_rs/0.98.0", want: true},
  61. {name: "codex_vscode 前缀", ua: "codex_vscode/1.0.0", want: true},
  62. {name: "codex_vscode_copilot 变体前缀", ua: "codex_vscode_copilot/0.140.0", want: true},
  63. {name: "codex_app 前缀", ua: "codex_app/0.1.0", want: true},
  64. {name: "codex_chatgpt_desktop 前缀", ua: "codex_chatgpt_desktop/1.0.0", want: true},
  65. {name: "codex_atlas 前缀", ua: "codex_atlas/1.0.0", want: true},
  66. {name: "codex_exec 前缀", ua: "codex_exec/0.1.0", want: true},
  67. {name: "codex_sdk_ts 前缀", ua: "codex_sdk_ts/0.1.0", want: true},
  68. {name: "Codex 桌面 UA", ua: "Codex Desktop/1.2.3", want: true},
  69. {name: "codex-tui 连字符前缀(真实流量占比最高)", ua: "codex-tui/0.141.0 (Mac OS 15.5.0; arm64) ghostty/1.3.1 (codex-tui; 0.141.0)", want: true},
  70. {name: "复合 UA 包含 codex_app", ua: "Mozilla/5.0 codex_app/0.1.0", want: true},
  71. {name: "大小写混合", ua: "Codex_VSCode/1.2.3", want: true},
  72. // UA 尾部兜底:cccc 是生产中 CODEX_INTERNAL_ORIGINATOR_OVERRIDE=cccc 的真实 codex-tui。
  73. // 审计 10GB/23天 中占非 codex 的 80.9%(494/611)、全 openai 流量的 5.3%——若不兜底会误杀。
  74. {name: "cccc override Mac → 尾部兜底放行", ua: "cccc/0.141.0 (Mac OS 14.6.1; arm64) Apple_Terminal/453 (codex-tui; 0.141.0)", want: true},
  75. {name: "cccc override Ubuntu → 尾部兜底放行", ua: "cccc/0.139.0 (Ubuntu 22.4.0; x86_64) screen (codex-tui; 0.139.0)", want: true},
  76. {name: "cccc override iTerm → 尾部兜底放行", ua: "cccc/0.137.0 (Mac OS 26.1.0; arm64) iTerm.app/3.4.22 (codex-tui; 0.137.0)", want: true},
  77. // 非 codex 尾部不应放行
  78. {name: "完全伪造尾部应拒", ua: "evil/0.1.0 (Linux; x86_64) bash (evil; 0.1.0)", want: false},
  79. {name: "非 codex", ua: "curl/8.0.1", want: false},
  80. {name: "空字符串", ua: "", want: false},
  81. }
  82. for _, tt := range tests {
  83. t.Run(tt.name, func(t *testing.T) {
  84. got := IsCodexOfficialClientRequest(tt.ua)
  85. if got != tt.want {
  86. t.Fatalf("IsCodexOfficialClientRequest(%q) = %v, want %v", tt.ua, got, tt.want)
  87. }
  88. })
  89. }
  90. }
  91. func TestIsCodexOfficialClientOriginator(t *testing.T) {
  92. tests := []struct {
  93. name string
  94. originator string
  95. want bool
  96. }{
  97. {name: "codex_cli_rs", originator: "codex_cli_rs", want: true},
  98. {name: "codex_vscode", originator: "codex_vscode", want: true},
  99. {name: "codex_app", originator: "codex_app", want: true},
  100. {name: "codex_chatgpt_desktop", originator: "codex_chatgpt_desktop", want: true},
  101. {name: "codex_atlas", originator: "codex_atlas", want: true},
  102. {name: "codex_exec", originator: "codex_exec", want: true},
  103. {name: "codex_sdk_ts", originator: "codex_sdk_ts", want: true},
  104. {name: "Codex 前缀", originator: "Codex Desktop", want: true},
  105. {name: "codex-tui 连字符(真实流量占比最高)", originator: "codex-tui", want: true},
  106. {name: "空白包裹", originator: " codex_vscode ", want: true},
  107. {name: "伪造含 codex_ 子串应拒(L2 收紧)", originator: "evil-codex_cli", want: false},
  108. {name: "codex_ 混入中段应拒(L2 收紧)", originator: "my_codex_thing", want: false},
  109. {name: "非 codex", originator: "my_client", want: false},
  110. {name: "空字符串", originator: "", want: false},
  111. }
  112. for _, tt := range tests {
  113. t.Run(tt.name, func(t *testing.T) {
  114. got := IsCodexOfficialClientOriginator(tt.originator)
  115. if got != tt.want {
  116. t.Fatalf("IsCodexOfficialClientOriginator(%q) = %v, want %v", tt.originator, got, tt.want)
  117. }
  118. })
  119. }
  120. }
  121. func TestIsCodexOfficialClientRequestStrict(t *testing.T) {
  122. tests := []struct {
  123. name string
  124. ua string
  125. want bool
  126. }{
  127. // 前缀开头:与 lax 版一致放行
  128. {name: "codex_cli_rs 前缀开头", ua: "codex_cli_rs/0.141.0 (x)", want: true},
  129. {name: "codex_vscode 前缀开头", ua: "codex_vscode/1.0.0", want: true},
  130. {name: "codex_app 前缀开头", ua: "codex_app/2.1.0", want: true},
  131. {name: "Codex 家族前缀保留", ua: "Codex Desktop/1.2.3", want: true},
  132. {name: "大小写混合前缀开头", ua: "Codex_CLI_Rs/0.141.0", want: true},
  133. // UA 尾部兜底保留:cccc override 真实 codex-tui 仍放行
  134. {name: "cccc override 尾部兜底仍放行", ua: "cccc/0.141.0 (Mac OS 14.6.1; arm64) Apple_Terminal/453 (codex-tui; 0.141.0)", want: true},
  135. // N1 收紧:codex token 不在行首(子串)不再算官方——lax 版会因 Contains 误判 true
  136. {name: "浏览器前缀+中段 codex_app 收紧→拒", ua: "Mozilla/5.0 codex_app/0.141.0", want: false},
  137. {name: "中段 codex_cli_rs 收紧→拒", ua: "evilclient/1.0 codex_cli_rs/0.141.0", want: false},
  138. {name: "非 codex", ua: "curl/8.0.1", want: false},
  139. {name: "空字符串", ua: "", want: false},
  140. }
  141. for _, tt := range tests {
  142. t.Run(tt.name, func(t *testing.T) {
  143. got := IsCodexOfficialClientRequestStrict(tt.ua)
  144. if got != tt.want {
  145. t.Fatalf("IsCodexOfficialClientRequestStrict(%q) = %v, want %v", tt.ua, got, tt.want)
  146. }
  147. })
  148. }
  149. }
  150. func TestIsCodexOfficialClientByHeaders(t *testing.T) {
  151. tests := []struct {
  152. name string
  153. ua string
  154. originator string
  155. want bool
  156. }{
  157. {name: "仅 originator 命中 desktop", originator: "Codex Desktop", want: true},
  158. {name: "仅 originator 命中 vscode", originator: "codex_vscode", want: true},
  159. {name: "仅 ua 命中 desktop", ua: "Codex Desktop/1.2.3", want: true},
  160. {name: "仅 originator 命中 codex-tui", originator: "codex-tui", want: true},
  161. {name: "仅 ua 命中 codex-tui", ua: "codex-tui/0.141.0 (Mac OS 15.5.0; arm64) ghostty/1.3.1", want: true},
  162. // cccc:originator 不命中精确集,但 UA 尾部兜底恢复真实 codex-tui
  163. {name: "cccc override → UA 尾部兜底放行(审计 5.3% 误杀场景)", ua: "cccc/0.141.0 (Mac OS 14.6.1; arm64) Apple_Terminal/453 (codex-tui; 0.141.0)", originator: "cccc", want: true},
  164. {name: "ua 与 originator 都未命中", ua: "curl/8.0.1", originator: "my_client", want: false},
  165. }
  166. for _, tt := range tests {
  167. t.Run(tt.name, func(t *testing.T) {
  168. got := IsCodexOfficialClientByHeaders(tt.ua, tt.originator)
  169. if got != tt.want {
  170. t.Fatalf("IsCodexOfficialClientByHeaders(%q, %q) = %v, want %v", tt.ua, tt.originator, got, tt.want)
  171. }
  172. })
  173. }
  174. }