Skip to content

feat: add proxy integration tests and fix Codex↔Gemini converter bug#393

Merged
ymkiux merged 1 commit intomainfrom
feat/proxy-integration-tests
Mar 11, 2026
Merged

feat: add proxy integration tests and fix Codex↔Gemini converter bug#393
ymkiux merged 1 commit intomainfrom
feat/proxy-integration-tests

Conversation

@Bowl42
Copy link
Collaborator

@Bowl42 Bowl42 commented Mar 11, 2026

Summary

  • Add comprehensive end-to-end proxy integration tests covering all 4 protocol types (Claude, OpenAI, Codex, Gemini) with 20 test cases:
    • 4 passthrough tests (each protocol to same-type upstream)
    • 12 cross-protocol conversion tests (all bidirectional pairs: Claude↔OpenAI, Claude↔Codex, Claude↔Gemini, OpenAI↔Codex, OpenAI↔Gemini, Codex↔Gemini)
    • 2 error handling tests (upstream 429 error propagation, no matching route)
    • 1 multi-protocol coexistence test (4 protocols with cross-routing in single env)
  • Fix production bug: Codex↔Gemini response transformers were registered with swapped implementations in codex_to_gemini.go and gemini_to_codex.go, causing responses to be silently returned in the wrong format with null/zero fields

Bug Details

The RegisterConverter() calls in codex_to_gemini.go and gemini_to_codex.go had their response transformers swapped:

  • codexToGeminiResponse.Transform() was reading GeminiResponse and outputting CodexResponse (Gemini→Codex), but was registered at responses[codex][gemini] which should convert Codex→Gemini
  • geminiToCodexResponse.Transform() was reading CodexResponse and outputting GeminiResponse (Codex→Gemini), but was registered at responses[gemini][codex] which should convert Gemini→Codex

This caused the ConvertingResponseWriter.Finalize() to unmarshal upstream responses into the wrong struct type, producing zero-valued output that was silently accepted (no error returned).

Test plan

  • All 20 proxy integration tests pass
  • Tests stable across 3 consecutive runs
  • Full test suite (go test ./...) passes (only pre-existing TestInviteCodeConsume_Concurrent flake)
  • No changes to test infrastructure affect existing e2e tests

🤖 Generated with Claude Code

Summary by CodeRabbit

发布说明

  • 测试

    • 添加端到端代理集成测试,验证多协议路由和跨协议转换的正确性
    • 新增代理测试环境配置,确保系统可靠性
  • 改进

    • 优化协议转换器配置,确保多协议间的准确映射

…rter bug

Add comprehensive end-to-end proxy integration tests covering all 4 protocol
types (Claude, OpenAI, Codex, Gemini) with 20 test cases including passthrough,
all 12 cross-protocol conversions, error handling, and multi-protocol coexistence.

Fix a bug where Codex↔Gemini response transformers were registered with swapped
implementations, causing silent data corruption: responses would be returned in
the wrong format with null/zero fields instead of being properly converted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 11, 2026

📝 Walkthrough

Walkthrough

该 PR 修复了 Codex 和 Gemini 协议转换器的响应转换函数注册,确保正确的方向映射。同时新增了全面的端到端测试套件,验证多协议路由、跨协议转换和错误处理场景。

Changes

Cohort / File(s) Summary
Protocol Converter Fixes
internal/converter/codex_to_gemini.go, internal/converter/gemini_to_codex.go
交换响应转换函数注册,确保 responses[from][to] 正确地从源格式转换到目标格式。Codex→Gemini 现使用 geminiToCodexResponse;Gemini→Codex 现使用 codexToGeminiResponse,并补充了说明转换方向的注释。
E2E Test Infrastructure
tests/e2e/proxy_setup_test.go
新增 ProxyTestEnv 测试环境,初始化内存 SQLite 数据库、所有必需仓库、缓存、认证、服务、路由和 HTTP 服务器,提供完整的端到端测试基础设施和清理机制。
E2E Integration Tests
tests/e2e/proxy_integration_test.go
新增 752 行全面测试套件,包括多个协议的 mock 上游服务器、12 种跨协议转换测试、路径转发验证、错误处理和协议共存场景。验证请求格式、响应转换和路由匹配功能。

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • awsl233777
  • dreamhunter2333

Poem

🐰 ✨ 转换器校正,方向理分明,
协议互相转,路由配得当,
测试全覆盖,多重验证精,
跨协同共舞,端端信满满!🎯

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题清晰准确地概括了拉取请求的两个主要变化:添加代理集成测试和修复Codex↔Gemini转换器bug。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/proxy-integration-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
internal/converter/codex_to_gemini.go (1)

12-14: 建议把 response transformer 按真实数据流重命名。

这里能工作,是因为 geminiToCodexResponse 实际上消费的是 Codex 响应并产出 Gemini 响应,而另一个文件里的 codexToGeminiResponse 正好相反。类型名和真实方向仍然是反着的,这次修完后后续维护者还是很容易把注册再写反。建议改成按响应体方向命名,或者把 request/response 的注册拆成两个显式 helper。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/converter/codex_to_gemini.go` around lines 12 - 14, The response
transformer name is inverted: RegisterConverter(domain.ClientTypeCodex,
domain.ClientTypeGemini, &codexToGeminiRequest{}, &geminiToCodexResponse{})
registers a transformer that actually consumes a Codex response and produces a
Gemini response; rename the type to reflect the real data flow (e.g.,
geminiResponseFromCodex or codexResponseToGemini) and update its declaration and
usages so the name matches "response body direction" (also update the
counterpart type in the other file, codexToGeminiResponse, to the
opposite-direction name), or alternatively replace this call with an explicit
helper that registers request and response transformers separately to avoid
future confusion; ensure all references to geminiToCodexResponse and
codexToGeminiResponse are updated accordingly.
tests/e2e/proxy_integration_test.go (1)

563-593: 建议给 Codex↔Gemini 再补一组流式回归用例。

这次修的是 response transformer 的注册,而 Transform()TransformChunk() 共用同一条 registry 映射。当前两条回归只覆盖普通 JSON 响应,SSE 路径如果以后再注册反了,这组测试仍然会全绿。建议两个方向各补一个 stream=true 断言,直接校验返回事件形态。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/proxy_integration_test.go` around lines 563 - 593, Add two
additional E2E cases mirroring "Codex_client_to_Gemini_upstream" and
"Gemini_client_to_Codex_upstream" but exercising the SSE/streaming path (set
stream=true on the client request), because Transform() and TransformChunk()
share the same registry mapping; for each new case use the same
clientType/clientReq/upstreamMock pairs and expectedUpPath/expectedUpField, and
in respAssert assert streaming event shape (e.g., presence of SSE event markers
or "event" fields and that body events follow the target format: Codex events
contain "object":"response" and Gemini events include "candidates") so the
transformer registration for chunks is validated separately from the JSON path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/e2e/proxy_integration_test.go`:
- Around line 636-659: In TestProxyUpstreamError (and the similar test around
lines 661-669), tighten assertions to assert the exact expected HTTP status
codes (e.g., expect http.StatusTooManyRequests/429 for the upstream rate-limit
case and the exact code for the "no matching route" case) instead of just
checking resp.StatusCode != http.StatusOK; additionally decode resp.Body (using
the same shape produced by the mock/openai response used in openaiRequest) and
assert key fields (e.g., error.type == "rate_limit_error" and error.message
contains "Rate limit exceeded" for the 429 case) to ensure the error body is
passed through unchanged; locate these changes in the TestProxyUpstreamError
test function and the analogous test that calls env.ProxyPost and uses
createProvider/createRoute.
- Around line 57-61: 在 tests/e2e/proxy_integration_test.go 中处理 JSON 反序列化时应在读取前立即
defer resp.Body.Close() 并检查 Decode 错误:在创建局部结果变量(result)并调用
json.NewDecoder(resp.Body).Decode(&result) 的位置(包括两处出现的代码块)改为先 defer
resp.Body.Close(),然后将 Decode 的返回值赋给 err 并在 err != nil 时使用 t.Fatalf 报告详细错误信息(包含
err 和上下文字符串),以避免在 decode 失败时静默继续导致误导性后续错误。

In `@tests/e2e/proxy_setup_test.go`:
- Around line 87-95: The test setup swallows errors from all cache preloads
(e.g., cachedProviderRepo.Load, cachedRouteRepo.Load,
cachedRetryConfigRepo.Load, cachedRoutingStrategyRepo.Load,
cachedProjectRepo.Load, cachedAPITokenRepo.Load, cachedModelMappingRepo.Load);
change each ignored call to check the returned error and call t.Fatalf with a
clear message including the failing repo name and the error (for example, if err
:= cachedProviderRepo.Load(); err != nil { t.Fatalf("cachedProviderRepo.Load
failed: %v", err) }) so test setup fails fast and surfaces the real preload
failure.

---

Nitpick comments:
In `@internal/converter/codex_to_gemini.go`:
- Around line 12-14: The response transformer name is inverted:
RegisterConverter(domain.ClientTypeCodex, domain.ClientTypeGemini,
&codexToGeminiRequest{}, &geminiToCodexResponse{}) registers a transformer that
actually consumes a Codex response and produces a Gemini response; rename the
type to reflect the real data flow (e.g., geminiResponseFromCodex or
codexResponseToGemini) and update its declaration and usages so the name matches
"response body direction" (also update the counterpart type in the other file,
codexToGeminiResponse, to the opposite-direction name), or alternatively replace
this call with an explicit helper that registers request and response
transformers separately to avoid future confusion; ensure all references to
geminiToCodexResponse and codexToGeminiResponse are updated accordingly.

In `@tests/e2e/proxy_integration_test.go`:
- Around line 563-593: Add two additional E2E cases mirroring
"Codex_client_to_Gemini_upstream" and "Gemini_client_to_Codex_upstream" but
exercising the SSE/streaming path (set stream=true on the client request),
because Transform() and TransformChunk() share the same registry mapping; for
each new case use the same clientType/clientReq/upstreamMock pairs and
expectedUpPath/expectedUpField, and in respAssert assert streaming event shape
(e.g., presence of SSE event markers or "event" fields and that body events
follow the target format: Codex events contain "object":"response" and Gemini
events include "candidates") so the transformer registration for chunks is
validated separately from the JSON path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7896739b-8056-4808-9ae1-f43d3bbeaee3

📥 Commits

Reviewing files that changed from the base of the PR and between 9bca42d and 9697f4a.

📒 Files selected for processing (4)
  • internal/converter/codex_to_gemini.go
  • internal/converter/gemini_to_codex.go
  • tests/e2e/proxy_integration_test.go
  • tests/e2e/proxy_setup_test.go
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: playwright

@ymkiux ymkiux merged commit 4e7df8d into main Mar 11, 2026
3 checks passed
@ymkiux ymkiux deleted the feat/proxy-integration-tests branch March 11, 2026 18:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants