Skip to content

test: migrate route service upstream coverage to ginkgo e2e#24

Merged
moonming merged 7 commits intoapi7:mainfrom
guoqqqi:test/e2e-route-service-upstream
Apr 20, 2026
Merged

test: migrate route service upstream coverage to ginkgo e2e#24
moonming merged 7 commits intoapi7:mainfrom
guoqqqi:test/e2e-route-service-upstream

Conversation

@guoqqqi
Copy link
Copy Markdown
Contributor

@guoqqqi guoqqqi commented Apr 12, 2026

Summary

  • migrate route, service, and upstream command coverage from mocked Admin API tests to real Ginkgo E2E coverage
  • keep only local unit tests for CLI validation and self-contained logic
  • add the shared Ginkgo E2E suite bootstrap and document the updated testing strategy

Validation

  • go test ./pkg/cmd/route/... ./pkg/cmd/service/... ./pkg/cmd/upstream/...
  • go test -c -tags e2e ./test/e2e
  • go test -tags e2e ./test/e2e -run TestE2E -ginkgo.focus="route command" -ginkgo.v
  • go test -tags e2e ./test/e2e -run TestE2E -ginkgo.focus="service command" -ginkgo.v
  • go test -tags e2e ./test/e2e -run TestE2E -ginkgo.focus="upstream command" -ginkgo.v

Summary by CodeRabbit

  • New Features

    • Added comprehensive Ginkgo/Gomega end-to-end suites exercising CLI flows against a live APISIX instance.
    • E2E environment now maps host.docker.internal for container-host access and documents APISIX_CONTROL_URL.
  • Refactor

    • Testing strategy revised: Admin-API–dependent CLI behavior must be covered by real e2e tests; unit tests limited to deterministic, self-contained logic.
    • E2E structure standardized with suite/bootstrap/spec conventions.
  • Chores

    • Added Ginkgo v2 and Gomega as test dependencies.

Copilot AI review requested due to automatic review settings April 12, 2026 13:21
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Migrates CLI command tests from httpmock-based unit suites to Ginkgo/Gomega end-to-end suites against real APISIX; deletes many mocked command tests, adds small local argument/TTY unit checks, updates docs, go.mod, e2e bootstrap, helpers, and comprehensive Ginkgo e2e specs.

Changes

Cohort / File(s) Summary
Documentation & deps
AGENTS.md, docs/testing-strategy.md, go.mod
Updated testing policy to forbid mocked Admin-API command unit tests and require Ginkgo/Gomega e2e coverage; added ginkgo/gomega deps and bumped transitive golang.org/x modules.
Route command tests
pkg/cmd/route/.../create_test.go, pkg/cmd/route/.../get_test.go, pkg/cmd/route/.../list_test.go, pkg/cmd/route/.../update_test.go (removed), pkg/cmd/route/delete/delete_test.go (removed), pkg/cmd/route/delete/delete_local_test.go, pkg/cmd/route/get/get_local_test.go, pkg/cmd/route/update/update_local_test.go (added)
Removed httpmock-based route command suites; added lightweight local tests for argument/non-TTY validation and flag exclusivity.
Service command tests
pkg/cmd/service/create/create_test.go, pkg/cmd/service/export/export_test.go, pkg/cmd/service/list/list_test.go (removed)
Deleted mocked service command tests; coverage relocated to e2e suites.
Upstream command tests
pkg/cmd/upstream/.../create_test.go, pkg/cmd/upstream/.../delete_test.go, pkg/cmd/upstream/.../export_test.go, pkg/cmd/upstream/.../get_test.go, pkg/cmd/upstream/.../health_test.go, pkg/cmd/upstream/.../list_test.go, pkg/cmd/upstream/.../update_test.go (removed), pkg/cmd/upstream/delete/delete_local_test.go, pkg/cmd/upstream/get/get_local_test.go, pkg/cmd/upstream/health/health_local_test.go, pkg/cmd/upstream/update/update_local_test.go (added)
Removed many httpmock-based upstream command tests; added focused local tests for argument validation and helper URL-building logic.
E2E bootstrap & docker
test/e2e/suite_test.go, test/e2e/ginkgo_helpers_test.go, test/e2e/docker-compose.yml
Added Ginkgo suite entrypoint, e2e helpers for CLI/env/tmp files and cleanup, and docker extra_hosts mapping for host access from containers.
Comprehensive E2E suites
test/e2e/route_ginkgo_test.go, test/e2e/service_ginkgo_test.go, test/e2e/upstream_ginkgo_test.go, test/e2e/scenario_coverage_ginkgo_test.go
Introduced Ginkgo/Gomega end-to-end tests exercising CLI against real APISIX Admin/Control/Gateway: create/list/get/update/delete/export flows, output format checks, filtering, auth error cases, health checks, and scenario traffic coverage.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant TestRunner as "Test runner (Ginkgo/Gomega)"
participant CLI as "a6 CLI"
participant Admin as "APISIX Admin API"
participant Control as "APISIX Control API"
participant Gateway as "APISIX Gateway"
rect rgba(200,200,255,0.5)
TestRunner->>CLI: spawn CLI with A6_CONFIG_DIR/env
CLI->>Admin: Admin API requests (create/get/list/update/delete/export)
Admin-->>CLI: API responses
CLI->>TestRunner: stdout/stderr/exit code
CLI->>Control: (health commands) control API calls
Control-->>CLI: health responses
Gateway-->>Control: runtime polling / route traffic (scenario tests)
end

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
E2e Test Quality Review ⚠️ Warning E2E tests lack proper error handling for HTTP response body reads and JSON operations without error verification, creating potential silent failures in test assertions. Add proper error checking for all json.Unmarshal(), ReadAll(), and Close() calls in E2E tests using Gomega assertions like Expect(err).NotTo(HaveOccurred()).
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: migrating route, service, and upstream command test coverage from mocked unit tests to real Ginkgo end-to-end tests.
Security Check ✅ Passed Pull request successfully migrates to real end-to-end tests with proper credential isolation, no unencrypted persistence, and standard error logging patterns.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit 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
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR migrates CLI command coverage for route, service, and upstream from mocked Admin API unit tests to real end-to-end coverage using a shared Ginkgo E2E suite, aligning tests with actual APISIX behavior.

Changes:

  • Adds a Ginkgo/Gomega E2E suite bootstrap and new Ginkgo specs for route, service, and upstream commands.
  • Replaces mocked Admin API command tests with smaller “local” unit tests for purely self-contained validation paths.
  • Updates E2E docker-compose and documents the revised testing strategy.

Reviewed changes

Copilot reviewed 31 out of 32 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/e2e/suite_test.go Adds Ginkgo suite bootstrap (RunSpecs) for the E2E package.
test/e2e/route_ginkgo_test.go New Ginkgo E2E coverage for route command behaviors against real APISIX.
test/e2e/service_ginkgo_test.go New Ginkgo E2E coverage for service command behaviors against real APISIX.
test/e2e/upstream_ginkgo_test.go New Ginkgo E2E coverage for upstream behaviors, including Control API health.
test/e2e/scenario_coverage_test.go Adds scenario-style E2E tests covering multi-resource interactions and real traffic.
test/e2e/docker-compose.yml Makes local E2E stack ports configurable and adds host.docker.internal mapping.
pkg/cmd/**/*_local_test.go Keeps only local/unit tests for CLI validation & deterministic helpers.
go.mod / go.sum Adds Ginkgo/Gomega dependencies for the E2E suite.
docs/testing-strategy.md Documents the new unit vs E2E testing split and Ginkgo-based E2E approach.
AGENTS.md Updates repository/testing guidance to reflect the E2E-first approach for APISIX behavior.
Comments suppressed due to low confidence (1)

docs/testing-strategy.md:146

  • This doc now states e2e tests should be written with Ginkgo/Gomega, but the “Assertions” section still implies testify as the default assertion library. Consider clarifying that unit tests use testify while Ginkgo e2e specs use Gomega matchers, or update this section to cover both to avoid mixed guidance.
## Assertions
Use the `testify` library for assertions:
```go
import (
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

require.NoError(t, err)           // Fatal if an error occurs
assert.Equal(t, expected, actual) // Continue if the assertion fails
assert.Contains(t, output, "ID")  // Check for a substring
</details>



---

💡 <a href="/api7/a6/new/main?filename=.github/instructions/*.instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Add Copilot custom instructions</a> for smarter, more guided reviews. <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Learn how to get started</a>.

Comment on lines +379 to +386
time.Sleep(3 * time.Second)
for range 5 {
resp, reqErr := http.Get(gatewayURL + "/ginkgo-upstream-health/get")
if reqErr == nil && resp != nil {
g.Expect(resp.Body.Close()).To(Succeed())
}
time.Sleep(500 * time.Millisecond)
}
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

for range 5 { ... } does not compile in Go (including go 1.22.x in this repo). Replace it with a standard counted loop (e.g., for i := 0; i < 5; i++ { ... }).

Copilot uses AI. Check for mistakes.
Comment thread test/e2e/docker-compose.yml Outdated
Comment on lines +14 to +15
- "${A6_E2E_GATEWAY_PORT:-19080}:9080"
- "${A6_E2E_CONTROL_PORT:-19090}:9090"
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

The docker-compose default host port mappings here (gateway/control defaulting to 19080/19090) don’t match the e2e test defaults in test/e2e/setup_test.go (APISIX_GATEWAY_URL defaults to :9080 and APISIX_CONTROL_URL defaults to :9090) or the make docker-up healthcheck URL. With the current defaults, make test-e2e against make docker-up will hit the wrong ports. Consider keeping the compose defaults aligned (9080/9090), or update make docker-up to export the corresponding env vars (APISIX_GATEWAY_URL/APISIX_CONTROL_URL) and update docs accordingly.

Suggested change
- "${A6_E2E_GATEWAY_PORT:-19080}:9080"
- "${A6_E2E_CONTROL_PORT:-19090}:9090"
- "${A6_E2E_GATEWAY_PORT:-9080}:9080"
- "${A6_E2E_CONTROL_PORT:-9090}:9090"

Copilot uses AI. Check for mistakes.
Comment thread test/e2e/docker-compose.yml Outdated
restart: always
ports:
- "8080:8080"
- "${A6_E2E_HTTPBIN_PORT:-18080}:8080"
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

The httpbin host port default is changed to 18080, but the e2e tests default HTTPBIN_URL is still http://127.0.0.1:8080 (see test/e2e/setup_test.go). This makes local e2e runs against make docker-up use the wrong httpbin port unless users manually override env vars. Align the default mapping back to 8080 or ensure make docker-up/docs set HTTPBIN_URL to match.

Suggested change
- "${A6_E2E_HTTPBIN_PORT:-18080}:8080"
- "${A6_E2E_HTTPBIN_PORT:-8080}:8080"

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
AGENTS.md (1)

109-123: ⚠️ Potential issue | 🟡 Minor

Please document the full E2E env contract (control URL + compose port override vars).

This section is the contract for local E2E setup, but it doesn’t explicitly cover APISIX_CONTROL_URL or the A6_E2E_*_PORT knobs introduced in compose defaults. Adding them here will prevent setup drift/confusion.

Suggested doc addition
 **Environment variables (for e2e tests):**
 | Variable | Default | Purpose |
 |---|---|---|
 | `APISIX_ADMIN_URL` | `http://127.0.0.1:9180` | Admin API base URL |
 | `APISIX_GATEWAY_URL` | `http://127.0.0.1:9080` | Data plane base URL |
+| `APISIX_CONTROL_URL` | `http://127.0.0.1:9090` | Control API base URL |
 | `HTTPBIN_URL` | `http://127.0.0.1:8080` | httpbin upstream URL |
+| `A6_E2E_ADMIN_PORT` | `9180` | Host admin port used by `test/e2e/docker-compose.yml` |
+| `A6_E2E_GATEWAY_PORT` | `9080` | Host gateway port used by `test/e2e/docker-compose.yml` |
+| `A6_E2E_CONTROL_PORT` | `9090` | Host control port used by `test/e2e/docker-compose.yml` |
+| `A6_E2E_HTTPBIN_PORT` | `8080` | Host httpbin port used by `test/e2e/docker-compose.yml` |

Based on learnings: Every new feature must update the documentation as per docs/documentation-maintenance.md.

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

In `@AGENTS.md` around lines 109 - 123, The AGENTS.md E2E section lacks the full
environment contract: add explicit documentation of the APISIX_CONTROL_URL env
var (purpose, default value used by tests, and example like
http://127.0.0.1:9180), and document the compose override variables introduced
(A6_E2E_APISIX_PORT, A6_E2E_HTTPBIN_PORT, etc. — list each A6_E2E_*_PORT name
and its default) and how to override them for local `make docker-up`/CI; place
this content into the Local development section near the make commands and link
to test/e2e/setup_test.go and test/e2e/suite_test.go as the canonical sources of
the defaults, and update the documentation-maintenance note to remind
contributors to update AGENTS.md when adding new E2E env knobs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/e2e/docker-compose.yml`:
- Around line 13-15: Update the docker-compose port default values so they match
the E2E test URL expectations: change the A6_E2E_GATEWAY_PORT default from 19080
to 9080, A6_E2E_CONTROL_PORT default from 19090 to 9090, and the httpbin host
port default (A6_E2E_HTTPBIN_PORT or the 18080 mapping referenced around the
same block) from 18080 to 8080; modify the port mappings in docker-compose.yml
(the lines containing "${A6_E2E_GATEWAY_PORT:...}",
"${A6_E2E_CONTROL_PORT:...}", and the httpbin mapping) so running make docker-up
&& make test-e2e works without manual env overrides.

---

Outside diff comments:
In `@AGENTS.md`:
- Around line 109-123: The AGENTS.md E2E section lacks the full environment
contract: add explicit documentation of the APISIX_CONTROL_URL env var (purpose,
default value used by tests, and example like http://127.0.0.1:9180), and
document the compose override variables introduced (A6_E2E_APISIX_PORT,
A6_E2E_HTTPBIN_PORT, etc. — list each A6_E2E_*_PORT name and its default) and
how to override them for local `make docker-up`/CI; place this content into the
Local development section near the make commands and link to
test/e2e/setup_test.go and test/e2e/suite_test.go as the canonical sources of
the defaults, and update the documentation-maintenance note to remind
contributors to update AGENTS.md when adding new E2E env knobs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 44552063-4ffb-48ba-8551-89f621cbdcb2

📥 Commits

Reviewing files that changed from the base of the PR and between 85d58d7 and 71df4bf.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (31)
  • AGENTS.md
  • docs/testing-strategy.md
  • go.mod
  • pkg/cmd/route/create/create_test.go
  • pkg/cmd/route/delete/delete_local_test.go
  • pkg/cmd/route/delete/delete_test.go
  • pkg/cmd/route/get/get_local_test.go
  • pkg/cmd/route/get/get_test.go
  • pkg/cmd/route/list/list_test.go
  • pkg/cmd/route/update/update_local_test.go
  • pkg/cmd/route/update/update_test.go
  • pkg/cmd/service/create/create_test.go
  • pkg/cmd/service/export/export_test.go
  • pkg/cmd/service/list/list_test.go
  • pkg/cmd/upstream/create/create_test.go
  • pkg/cmd/upstream/delete/delete_local_test.go
  • pkg/cmd/upstream/delete/delete_test.go
  • pkg/cmd/upstream/export/export_test.go
  • pkg/cmd/upstream/get/get_local_test.go
  • pkg/cmd/upstream/get/get_test.go
  • pkg/cmd/upstream/health/health_local_test.go
  • pkg/cmd/upstream/health/health_test.go
  • pkg/cmd/upstream/list/list_test.go
  • pkg/cmd/upstream/update/update_local_test.go
  • pkg/cmd/upstream/update/update_test.go
  • test/e2e/docker-compose.yml
  • test/e2e/route_ginkgo_test.go
  • test/e2e/scenario_coverage_test.go
  • test/e2e/service_ginkgo_test.go
  • test/e2e/suite_test.go
  • test/e2e/upstream_ginkgo_test.go
💤 Files with no reviewable changes (15)
  • pkg/cmd/route/list/list_test.go
  • pkg/cmd/upstream/create/create_test.go
  • pkg/cmd/service/create/create_test.go
  • pkg/cmd/route/get/get_test.go
  • pkg/cmd/route/update/update_test.go
  • pkg/cmd/upstream/get/get_test.go
  • pkg/cmd/service/list/list_test.go
  • pkg/cmd/upstream/update/update_test.go
  • pkg/cmd/upstream/delete/delete_test.go
  • pkg/cmd/service/export/export_test.go
  • pkg/cmd/upstream/list/list_test.go
  • pkg/cmd/upstream/health/health_test.go
  • pkg/cmd/route/create/create_test.go
  • pkg/cmd/upstream/export/export_test.go
  • pkg/cmd/route/delete/delete_test.go
👮 Files not reviewed due to content moderation or server errors (8)
  • pkg/cmd/route/delete/delete_local_test.go
  • pkg/cmd/upstream/delete/delete_local_test.go
  • pkg/cmd/upstream/health/health_local_test.go
  • test/e2e/route_ginkgo_test.go
  • docs/testing-strategy.md
  • test/e2e/upstream_ginkgo_test.go
  • test/e2e/service_ginkgo_test.go
  • test/e2e/scenario_coverage_test.go

Comment thread test/e2e/docker-compose.yml Outdated
Comment thread test/e2e/scenario_coverage_test.go Outdated
Comment thread test/e2e/route_ginkgo_test.go Outdated
Copilot AI review requested due to automatic review settings April 13, 2026 10:51
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 32 out of 33 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +80 to +86
for i := 0; i < 10; i++ {
resp, reqErr := http.Get(gatewayURL + "/scenario-combo")
g.Expect(reqErr).NotTo(HaveOccurred())

payload, readErr := io.ReadAll(resp.Body)
g.Expect(resp.Body.Close()).To(Succeed())
g.Expect(readErr).NotTo(HaveOccurred())
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

The retry loop defeats its own purpose by failing immediately on transient request errors (reqErr). This can make the spec flaky when APISIX/gateway isn’t ready yet (or during brief resets). Instead, treat request errors like non-200 responses: record the last error and continue/retry, then assert after the loop that the request eventually succeeded (and include the last error/status in the failure message).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated in d580e2c. The route+service+plugin-config scenario now retries on transient request errors instead of failing immediately, and the final assertion includes the last error/status for easier diagnosis.

Comment on lines +153 to +159
for i := 0; i < 10; i++ {
resp, reqErr := http.Get(gatewayURL + "/scenario-multi-node")
g.Expect(reqErr).NotTo(HaveOccurred())

payload, readErr := io.ReadAll(resp.Body)
g.Expect(resp.Body.Close()).To(Succeed())
g.Expect(readErr).NotTo(HaveOccurred())
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

Same issue in this retry loop: g.Expect(reqErr).NotTo(HaveOccurred()) will fail on the first transient connection error and prevent retries, making the load-balancing scenario flaky. Consider retrying on request errors (and optionally non-200 status codes) and asserting after the loop with a helpful last-error/last-status message.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated in d580e2c. The multi-node scenario uses the same retry pattern now: transient request errors are retried and the failure message reports the last error/status instead of failing on the first connection issue.

Comment on lines +182 to +191
func expectHeaderContains(g Gomega, raw interface{}, want string) {
switch v := raw.(type) {
case string:
g.Expect(v).To(Equal(want))
case []interface{}:
values := make([]string, 0, len(v))
for _, item := range v {
values = append(values, fmt.Sprintf("%v", item))
}
g.Expect(values).To(ContainElement(want))
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

expectHeaderContains asserts strict equality for the string case (Equal(want)), which doesn’t match the helper name and is less tolerant of upstream/header formatting differences. Either rename the helper to reflect equality semantics, or change the string assertion to a containment check (and keep the slice case as-is).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated in d580e2c. I changed the string branch in expectHeaderContains to use containment semantics so the helper behavior matches its name.

Comment thread docs/testing-strategy.md

E2E tests validate the CLI binary against a real APISIX instance. They live in `test/e2e/` and use the `//go:build e2e` build tag to separate them from unit tests.
E2E tests validate the CLI binary against a real APISIX instance. They live in `test/e2e/`, use the `//go:build e2e` build tag, and should be written with Ginkgo/Gomega.

Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

This section states e2e tests should use Ginkgo/Gomega, but the later “Assertions” section only documents testify. Please clarify the split (e.g., unit tests: testify; e2e specs: gomega/Expect) or update the assertions section to include Gomega examples to avoid confusing contributors.

Suggested change
Assertion style depends on the test type:
- Unit tests should use the standard `testing` package structure (`func TestXxx(t *testing.T)`) and may use `testify` assertions such as `require.NoError(t, err)` or `assert.Equal(t, expected, actual)`.
- E2E specs in `test/e2e/` should use Ginkgo/Gomega conventions and Gomega assertions such as `Expect(err).NotTo(HaveOccurred())` and `Expect(output).To(ContainSubstring("route"))`.
Do not treat `testify` as the default assertion style for e2e specs; use Gomega `Expect` assertions there so the framework guidance stays consistent.

Copilot uses AI. Check for mistakes.
Comment thread docs/testing-strategy.md
Comment on lines 50 to 58
### Architecture

The e2e framework:
1. Builds the `a6` binary once in `TestMain` (in `setup_test.go`)
1. Builds the `a6` binary once per test process
2. Waits for APISIX Admin API to become healthy
3. Runs tests that invoke the binary via `exec.Command`
4. Uses direct Admin API calls for setup/cleanup (not via the CLI)
3. Runs specs that invoke the binary via `exec.Command`
4. Uses direct Admin API calls only for setup and cleanup
5. Organizes scenarios with a suite structure inspired by Kubernetes-style e2e suites

Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

The updated e2e strategy (including upstream health checks) now relies on the APISIX Control API as well, but the documented Environment Variables list doesn’t include APISIX_CONTROL_URL (it’s supported by the e2e harness). Please add it to the docs so users know how to override the Control API base URL when running the suite.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/e2e/ginkgo_helpers_test.go`:
- Around line 60-74: The four helper functions deleteRouteViaCLIByID,
deleteServiceViaCLIByID, deleteUpstreamViaCLIByID, and
deletePluginConfigViaCLIByID currently discard the error returned by
runA6WithEnv; change each to surface and handle that error instead (e.g., return
error to the caller or call the test fail/assertion helper) so cleanup failures
are not suppressed. Locate runA6WithEnv usage in each function, capture its
error value, and either return it from the helper or call the appropriate
Ginkgo/Gomega fail/assert (so callers can react), and update call sites to
handle the returned error where needed. Ensure log messages include command
context (function name and id) when reporting the error.
- Around line 37-42: The helper deleteResourceViaAdminByID currently only closes
resp.Body when err == nil; change it to always close any non-nil response body
from adminAPI by checking resp != nil and deferring or calling resp.Body.Close()
regardless of err so the body is not leaked (refer to deleteResourceViaAdminByID
and adminAPI response handling: ensure resp != nil then defer resp.Body.Close()
immediately after the call or explicitly close on all return paths).

In `@test/e2e/scenario_coverage_ginkgo_test.go`:
- Around line 176-179: The test handler passed to httptest.NewServer ignores the
error returned by w.Write; update the inline http.HandlerFunc so the write
result is checked (e.g., _, err := w.Write([]byte(name)); if err != nil {
panic(fmt.Errorf("failed to write response: %w", err)) }) to surface failures
during tests; modify the handler inside the httptest.NewServer call accordingly.
- Around line 81-82: The test uses http.Get in polling loops which can block
indefinitely; create a timed HTTP client (client := &http.Client{Timeout: 2 *
time.Second}) and replace the two uses of http.Get(...) with client.Get(...) in
the polling loops (the calls currently using http.Get in the scenario coverage
test), ensuring the client is reused for each request so individual network
stalls will timeout instead of hanging the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 621a9714-9409-42d4-b1c3-769d828802e4

📥 Commits

Reviewing files that changed from the base of the PR and between 2a63793 and aa82d2f.

📒 Files selected for processing (6)
  • AGENTS.md
  • test/e2e/ginkgo_helpers_test.go
  • test/e2e/route_ginkgo_test.go
  • test/e2e/scenario_coverage_ginkgo_test.go
  • test/e2e/service_ginkgo_test.go
  • test/e2e/upstream_ginkgo_test.go
✅ Files skipped from review due to trivial changes (1)
  • test/e2e/service_ginkgo_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • AGENTS.md
  • test/e2e/upstream_ginkgo_test.go
  • test/e2e/route_ginkgo_test.go

Comment on lines +37 to +42
func deleteResourceViaAdminByID(g Gomega, resourcePath, id string) {
resp, err := adminAPI("DELETE", resourcePath+"/"+id, nil)
if err == nil && resp != nil {
g.Expect(resp.Body.Close()).To(Succeed())
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Close Admin API response bodies even on error paths.

On Line 39, resp.Body is closed only when err == nil. If adminAPI returns a non-nil response with an error (e.g., non-2xx), this can leak the body.

Proposed fix
 func deleteResourceViaAdminByID(g Gomega, resourcePath, id string) {
 	resp, err := adminAPI("DELETE", resourcePath+"/"+id, nil)
-	if err == nil && resp != nil {
+	if resp != nil {
 		g.Expect(resp.Body.Close()).To(Succeed())
 	}
+	g.Expect(err).NotTo(HaveOccurred())
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func deleteResourceViaAdminByID(g Gomega, resourcePath, id string) {
resp, err := adminAPI("DELETE", resourcePath+"/"+id, nil)
if err == nil && resp != nil {
g.Expect(resp.Body.Close()).To(Succeed())
}
}
func deleteResourceViaAdminByID(g Gomega, resourcePath, id string) {
resp, err := adminAPI("DELETE", resourcePath+"/"+id, nil)
if resp != nil {
g.Expect(resp.Body.Close()).To(Succeed())
}
g.Expect(err).NotTo(HaveOccurred())
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/ginkgo_helpers_test.go` around lines 37 - 42, The helper
deleteResourceViaAdminByID currently only closes resp.Body when err == nil;
change it to always close any non-nil response body from adminAPI by checking
resp != nil and deferring or calling resp.Body.Close() regardless of err so the
body is not leaked (refer to deleteResourceViaAdminByID and adminAPI response
handling: ensure resp != nil then defer resp.Body.Close() immediately after the
call or explicitly close on all return paths).

Comment on lines +60 to +74
func deleteRouteViaCLIByID(env []string, id string) {
_, _, _ = runA6WithEnv(env, "route", "delete", id, "--force")
}

func deleteServiceViaCLIByID(env []string, id string) {
_, _, _ = runA6WithEnv(env, "service", "delete", id, "--force")
}

func deleteUpstreamViaCLIByID(env []string, id string) {
_, _, _ = runA6WithEnv(env, "upstream", "delete", id, "--force")
}

func deletePluginConfigViaCLIByID(env []string, id string) {
_, _, _ = runA6WithEnv(env, "plugin-config", "delete", id, "--force")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not suppress CLI cleanup errors.

Lines 61/65/69/73 discard cleanup errors entirely. This can mask failed deletions and introduce flaky cross-test interference.

Proposed refactor
-func deleteRouteViaCLIByID(env []string, id string) {
-	_, _, _ = runA6WithEnv(env, "route", "delete", id, "--force")
+func deleteRouteViaCLIByID(g Gomega, env []string, id string) {
+	_, stderr, err := runA6WithEnv(env, "route", "delete", id, "--force")
+	g.Expect(err).NotTo(HaveOccurred(), "route cleanup failed: %s", stderr)
 }
 
-func deleteServiceViaCLIByID(env []string, id string) {
-	_, _, _ = runA6WithEnv(env, "service", "delete", id, "--force")
+func deleteServiceViaCLIByID(g Gomega, env []string, id string) {
+	_, stderr, err := runA6WithEnv(env, "service", "delete", id, "--force")
+	g.Expect(err).NotTo(HaveOccurred(), "service cleanup failed: %s", stderr)
 }
 
-func deleteUpstreamViaCLIByID(env []string, id string) {
-	_, _, _ = runA6WithEnv(env, "upstream", "delete", id, "--force")
+func deleteUpstreamViaCLIByID(g Gomega, env []string, id string) {
+	_, stderr, err := runA6WithEnv(env, "upstream", "delete", id, "--force")
+	g.Expect(err).NotTo(HaveOccurred(), "upstream cleanup failed: %s", stderr)
 }
 
-func deletePluginConfigViaCLIByID(env []string, id string) {
-	_, _, _ = runA6WithEnv(env, "plugin-config", "delete", id, "--force")
+func deletePluginConfigViaCLIByID(g Gomega, env []string, id string) {
+	_, stderr, err := runA6WithEnv(env, "plugin-config", "delete", id, "--force")
+	g.Expect(err).NotTo(HaveOccurred(), "plugin-config cleanup failed: %s", stderr)
 }

As per coding guidelines, **/*.go: Never suppress errors. Always handle and propagate them.

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

In `@test/e2e/ginkgo_helpers_test.go` around lines 60 - 74, The four helper
functions deleteRouteViaCLIByID, deleteServiceViaCLIByID,
deleteUpstreamViaCLIByID, and deletePluginConfigViaCLIByID currently discard the
error returned by runA6WithEnv; change each to surface and handle that error
instead (e.g., return error to the caller or call the test fail/assertion
helper) so cleanup failures are not suppressed. Locate runA6WithEnv usage in
each function, capture its error value, and either return it from the helper or
call the appropriate Ginkgo/Gomega fail/assert (so callers can react), and
update call sites to handle the returned error where needed. Ensure log messages
include command context (function name and id) when reporting the error.

Comment thread test/e2e/scenario_coverage_ginkgo_test.go Outdated
Comment thread test/e2e/scenario_coverage_ginkgo_test.go
Copy link
Copy Markdown

@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: 1

♻️ Duplicate comments (2)
test/e2e/scenario_coverage_ginkgo_test.go (2)

83-83: ⚠️ Potential issue | 🟠 Major

Add request timeouts to polling HTTP calls.

Both polling loops still use http.Get directly, so a single stalled request can hang the test even with bounded retries.

Proposed fix
@@
-		for i := 0; i < 10; i++ {
-			resp, reqErr := http.Get(gatewayURL + "/scenario-combo")
+		client := &http.Client{Timeout: 2 * time.Second}
+		for i := 0; i < 10; i++ {
+			resp, reqErr := client.Get(gatewayURL + "/scenario-combo")
@@
-		for i := 0; i < 10; i++ {
-			resp, reqErr := http.Get(gatewayURL + "/scenario-multi-node")
+		client := &http.Client{Timeout: 2 * time.Second}
+		for i := 0; i < 10; i++ {
+			resp, reqErr := client.Get(gatewayURL + "/scenario-multi-node")

Also applies to: 163-163

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

In `@test/e2e/scenario_coverage_ginkgo_test.go` at line 83, Replace the raw
http.Get calls used in the polling loops (the call producing resp, reqErr when
hitting gatewayURL + "/scenario-combo" and the duplicate at the other location)
with requests that enforce timeouts: create an http.Client with a sensible
Timeout or build each request using context.WithTimeout and use
http.NewRequestWithContext, then call client.Do; ensure you handle
context/timeouts as errors and close resp.Body in all paths. This change should
be applied to both occurrences that reference gatewayURL + "/scenario-combo" so
a single stalled request cannot hang the test.

190-193: ⚠️ Potential issue | 🟡 Minor

Do not suppress w.Write errors in test handler.

The handler currently drops the write error, which hides failures.

Proposed fix
 func newNamedTestServer(name string) *httptest.Server {
 	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		w.Header().Set("Content-Type", "text/plain")
-		_, _ = w.Write([]byte(name))
+		_, err := w.Write([]byte(name))
+		Expect(err).NotTo(HaveOccurred())
 	}))
 }

As per coding guidelines, **/*.go: Never suppress errors. Always handle and propagate them.

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

In `@test/e2e/scenario_coverage_ginkgo_test.go` around lines 190 - 193, The
handler inside the httptest.NewServer currently discards the result of w.Write;
replace the blank identifier with an error check (capture err := w.Write(...)),
and handle it instead of suppressing it — e.g., if err != nil { t.Fatalf("failed
to write response for %q: %v", name, err) } or otherwise propagate/log/panic
appropriately so write failures are not hidden; locate this change in the
anonymous http.HandlerFunc used with httptest.NewServer and the call to w.Write.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/e2e/scenario_coverage_ginkgo_test.go`:
- Around line 104-114: Replace the untyped JSON handling around the local
variable result with a concrete struct that maps the "url" field to string and
"headers" to json.RawMessage (or a typed map[string]string) so you avoid
map[string]interface{} and interface{} usage; unmarshal into that struct, parse
the url string into u as before, and decode the headers RawMessage into a typed
map used by expectHeaderContains when checking the "X-Scenario-Coverage" header.
Update the code paths that reference result["url"], result["headers"], and the
call expectHeaderContains(g, headers["X-Scenario-Coverage"],
"service-route-plugin-config") to use the new struct fields and typed header
value; apply the same replacement for the similar block around lines 196-208.

---

Duplicate comments:
In `@test/e2e/scenario_coverage_ginkgo_test.go`:
- Line 83: Replace the raw http.Get calls used in the polling loops (the call
producing resp, reqErr when hitting gatewayURL + "/scenario-combo" and the
duplicate at the other location) with requests that enforce timeouts: create an
http.Client with a sensible Timeout or build each request using
context.WithTimeout and use http.NewRequestWithContext, then call client.Do;
ensure you handle context/timeouts as errors and close resp.Body in all paths.
This change should be applied to both occurrences that reference gatewayURL +
"/scenario-combo" so a single stalled request cannot hang the test.
- Around line 190-193: The handler inside the httptest.NewServer currently
discards the result of w.Write; replace the blank identifier with an error check
(capture err := w.Write(...)), and handle it instead of suppressing it — e.g.,
if err != nil { t.Fatalf("failed to write response for %q: %v", name, err) } or
otherwise propagate/log/panic appropriately so write failures are not hidden;
locate this change in the anonymous http.HandlerFunc used with
httptest.NewServer and the call to w.Write.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b4338bd2-5b7b-434e-8194-a85b33cbaeac

📥 Commits

Reviewing files that changed from the base of the PR and between aa82d2f and d580e2c.

📒 Files selected for processing (1)
  • test/e2e/scenario_coverage_ginkgo_test.go

Comment thread test/e2e/scenario_coverage_ginkgo_test.go Outdated
Copilot AI review requested due to automatic review settings April 13, 2026 11:34
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 32 out of 33 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +195 to +200
func newNamedTestServer(name string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
_, err := w.Write([]byte(name))
Expect(err).NotTo(HaveOccurred())
}))
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

newNamedTestServer uses Expect(...) inside the httptest.Server handler goroutine. Gomega/Ginkgo assertions in non-test goroutines should be guarded with defer GinkgoRecover() (or avoid assertions entirely) to prevent panics from bypassing Ginkgo's fail handler and crashing/flaking the suite.

Copilot uses AI. Check for mistakes.
Comment on lines +352 to +359
time.Sleep(3 * time.Second)
for range 5 {
resp, reqErr := http.Get(gatewayURL + "/ginkgo-upstream-health/get")
if reqErr == nil && resp != nil {
g.Expect(resp.Body.Close()).To(Succeed())
}
time.Sleep(500 * time.Millisecond)
}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

This probe loop uses http.Get (default client) which has no timeout, so the spec can hang indefinitely if the gateway is slow/unreachable. Prefer an http.Client{Timeout: ...} here (similar to the scenario coverage specs) and treat request errors as retriable to reduce flakiness.

Copilot uses AI. Check for mistakes.
Comment on lines +146 to +153
stdout, stderr, err = runA6WithEnv(env, "service", "list", "--output", "json")
g.Expect(err).NotTo(HaveOccurred(), "stdout=%s stderr=%s", stdout, stderr)
g.Expect(json.Valid([]byte(stdout))).To(BeTrue(), stdout)
g.Expect(stdout).To(ContainSubstring(id1))

stdout, stderr, err = runA6WithEnv(env, "service", "list", "--output", "yaml")
g.Expect(err).NotTo(HaveOccurred(), "stdout=%s stderr=%s", stdout, stderr)
g.Expect(stdout).To(ContainSubstring("id: " + id1))
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

In this service list spec you create two services (id1 and id2), but the JSON/YAML assertions only check for id1. This can let regressions slip where non-table output omits entries. Please also assert id2 is present in the unfiltered JSON/YAML outputs (matching the table assertions).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
test/e2e/scenario_coverage_ginkgo_test.go (1)

84-106: Optional: extract shared polling loop to a helper.

Both scenarios implement near-identical retry/poll logic; extracting it would reduce duplication and make future tuning safer.

Also applies to: 164-188

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

In `@test/e2e/scenario_coverage_ginkgo_test.go` around lines 84 - 106, Extract the
duplicated polling/retry loop into a helper function (e.g., pollGetUntilOK) that
accepts an *http.Client, URL string, maxRetries int and delay time.Duration and
returns (body string, status int, err error); replace the inlined loop that uses
client := &http.Client{Timeout: 2 * time.Second}, the for i:=0;i<10;i++ retry
logic, io.ReadAll(resp.Body)/resp.Body.Close(), lastErr/lastStatus/body
handling, and sleeps with a single call to this helper in both places (the block
that currently assigns lastErr/lastStatus/body and breaks on http.StatusOK and
the similar block at lines 164-188). Ensure the helper closes the response body,
returns read errors and non-200 final status as errors, and preserves the
existing timeout and retry/delay behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/e2e/scenario_coverage_ginkgo_test.go`:
- Around line 84-106: Extract the duplicated polling/retry loop into a helper
function (e.g., pollGetUntilOK) that accepts an *http.Client, URL string,
maxRetries int and delay time.Duration and returns (body string, status int, err
error); replace the inlined loop that uses client := &http.Client{Timeout: 2 *
time.Second}, the for i:=0;i<10;i++ retry logic,
io.ReadAll(resp.Body)/resp.Body.Close(), lastErr/lastStatus/body handling, and
sleeps with a single call to this helper in both places (the block that
currently assigns lastErr/lastStatus/body and breaks on http.StatusOK and the
similar block at lines 164-188). Ensure the helper closes the response body,
returns read errors and non-200 final status as errors, and preserves the
existing timeout and retry/delay behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1bcc558e-729f-428d-a3f2-b45a23f0e31d

📥 Commits

Reviewing files that changed from the base of the PR and between d580e2c and acec7ff.

📒 Files selected for processing (1)
  • test/e2e/scenario_coverage_ginkgo_test.go

@guoqqqi guoqqqi requested a review from nic-6443 April 14, 2026 08:52
@moonming moonming merged commit 7cea718 into api7:main Apr 20, 2026
9 of 10 checks passed
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.

4 participants