Skip to content

test(e2e): add a7 CLI permutation / combination stability suite#58

Closed
shreemaan-abhishek wants to merge 2 commits into
masterfrom
test/permutation-cli-suite
Closed

test(e2e): add a7 CLI permutation / combination stability suite#58
shreemaan-abhishek wants to merge 2 commits into
masterfrom
test/permutation-cli-suite

Conversation

@shreemaan-abhishek
Copy link
Copy Markdown
Contributor

@shreemaan-abhishek shreemaan-abhishek commented May 27, 2026

Summary

Adds a new layered combinatorial test layer that drives the real `./bin/a7` binary against a live API7 EE 3.9 instance. ~290 cases run end-to-end in roughly 2 seconds; ginkgo signals overall pass/fail and the suite writes a paste-able `test/e2e/_artifacts/permutation-report.{json,md}` artifact listing every case with its command line, exit code, stdout/stderr digest, and duration.

Coverage tiers

Tier Coverage Approximate cases
1 Help integrity (`--help` and `-h` on every parent + leaf) 186
2 Version / completion / update help 7
3 Context lifecycle (create / list / current / use / delete) 8
4 Output-format matrix per read-only verb x `{table, json, yaml, invalid}` 13
5 Auth-source precedence (flag vs env vs context) 3
6 CRUD round-trip per resource (8 resources x ~7 steps) ~55
7 Declarative config dump -> validate -> diff (clean) 3
8 Negative / error matrix 11
9 Debug commands smoke 2
10 Unsupported commands stay unknown 4

How to run

```
A7_ADMIN_URL=https://localhost:7443 \
A7_TOKEN=a7ee-xxxx \
A7_GATEWAY_GROUP=default \
make test-e2e-permutation
```

Optional: set `A7_GATEWAY_URL` to also exercise the `debug trace` smoke.

Findings from the first three runs

Three CLI inconsistencies surfaced. Each is fixed in a separate small PR; once those merge, this suite goes 292/292 green:

Design choices

  • Drives the real binary via `exec.Command` and the existing `test/e2e/setup_test.go::runA7WithEnv` helper. Zero internal imports of `pkg/cmd/`; the suite sees `a7` exactly the way a human does.
  • Resource ids prefixed `a7-perm--`. Per-case defers clean up; an `AfterSuite` sweep matches the prefix regex as a backstop.
  • Sweep is tolerant of resource types where listing requires a parent id (e.g. routes need `--service-id`) or where the endpoint is not exposed on this EE build (secret on some builds). Returns a synthetic `errListUnsupported` that is silently dropped.
  • `Ordered, ContinueOnFailure` container so a tier failure does not skip the rest. Each tier is one `It` so granular failures are still visible.
  • Report is paste-able into a GitHub issue without rerunning. Markdown table per tier + a Failures section with reproducible command lines.

Test plan

Out of scope

  • Literal 2^N cartesian flag explosion
  • Property-based fuzz on top of the tiered combos
  • CI integration (target exists but is not yet wired into `.github/workflows/e2e.yml`; happy to follow up once the suite is stable on master)

Summary by CodeRabbit

  • Tests
    • Implemented a comprehensive end-to-end permutation test suite to systematically validate CLI commands, operations, and configuration workflows.
    • Added test reporting and artifact generation for improved test transparency and debugging insights.

Review Change Stack

A new layered combinatorial test that drives the real ./bin/a7 binary
against a live API7 EE 3.9 instance. ~290 cases run end-to-end in
~2 seconds; ginkgo signals overall pass/fail and the suite writes a
paste-able test/e2e/_artifacts/permutation-report.{json,md} artifact
listing every case with its command line, exit code, stdout/stderr
digest, and duration.

Coverage tiers:
  1. Help integrity        — every parent + every leaf, --help and -h
  2. Version / completion  — version, completion {bash,zsh,fish,ps1}
  3. Context lifecycle     — create/list/current/use/delete in isolated A7_CONFIG_DIR
  4. Output-format matrix  — every read-only verb x {table, json, yaml, invalid}
  5. Auth-source precedence — flag vs env vs context-file
  6. CRUD round-trip       — per-resource walker for service, route, consumer,
                              ssl (with runtime-generated ECDSA cert), stream-route,
                              proto, plus dedicated global-rule (upsert via update)
                              and secret (capability-gap probe)
  7. Declarative config    — dump -> validate -> diff (clean)
  8. Negative / error      — missing flag, bad ID, malformed file, wrong token,
                              wrong server, unsupported declarative sections
  9. Debug commands smoke
  10. Unsupported commands  — assert upstream, consumer-group, service-template,
                               plugin-config still error as unknown

Implementation notes:
- All resources use the a7-perm-<resource>-<nanoseconds> id prefix; per-case
  defers clean up; an AfterSuite sweep backstops any leftover ids.
- Sweep silently skips resource types where listing requires a parent id or
  the endpoint is not exposed on this EE build.
- Ginkgo container is Ordered+ContinueOnFailure so a failure in one tier does
  not skip the rest.
- Runs via the new `make test-e2e-permutation` target (ginkgo --focus).

Run with:
  A7_ADMIN_URL=https://localhost:7443 \
  A7_TOKEN=a7ee-xxxx \
  A7_GATEWAY_GROUP=default \
  make test-e2e-permutation

Adds test/e2e/_artifacts/ to .gitignore so per-run report files do not get
committed.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

Warning

Review limit reached

@shreemaan-abhishek, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 15 minutes and 56 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 31f13a7c-ec3a-4894-b6da-0ec11945ba7b

📥 Commits

Reviewing files that changed from the base of the PR and between 3d8db61 and 32cacdb.

📒 Files selected for processing (3)
  • Makefile
  • test/e2e/permutation_matrix_test.go
  • test/e2e/permutation_test.go
📝 Walkthrough

Walkthrough

This pull request introduces a comprehensive end-to-end Ginkgo test suite that exercises the a7 CLI through a tiered permutation matrix. The suite validates help output, version/completion, output formats across table/json/yaml, error handling, resource CRUD workflows, authentication precedence, and configuration management, while automatically recording results and cleaning up test artifacts.

Changes

E2E Permutation Test Suite

Layer / File(s) Summary
Build configuration and test target
.gitignore, Makefile
.gitignore excludes test/e2e/_artifacts/, and Makefile adds test-e2e-permutation target that runs Ginkgo with the e2e build tag, 60-minute timeout, and Permutation filter on ./test/e2e/....
Test case matrix builders
test/e2e/permutation_matrix_test.go
Defines permCase test work unit and hardcoded case generators for help integrity across leaf commands, version/completion output validation, output-format permutations (table/json/yaml) for read-only verbs, negative/error scenarios (malformed inputs, missing IDs, unsupported config sections), and unsupported command detection.
CRUD specification and test helpers
test/e2e/permutation_matrix_test.go
Introduces resourceSpec registry mapping service/route/consumer/ssl/stream-route/proto to JSON create payloads and per-type list/export/delete wiring; provides temp file writers (writeTempYAML, writeTempJSON), artifact directory resolver, SSL certificate generation using ECDSA P-256, and jsonEscape for safe PEM embedding in JSON.
Core test execution plumbing
test/e2e/permutation_test.go
Implements runCase to execute a7 CLI with per-case args/env and optional setup/cleanup, evaluateCase to derive pass/fail with failure-reason precedence, failsInTier counter, and generic runCRUDWalker to orchestrate create/get/list/export/delete workflows with format validation.
Tier-specific test workflows
test/e2e/permutation_test.go
Implements six tier workflows: context lifecycle (isolated config dir with create/list/use/delete), auth precedence (flag/env/file token ordering), global-rule upsert/CRUD, secrets capability probe with 404 fallback, declarative config dump/validate/diff, and debug smoke tests with conditional trace checks.
Ginkgo suite orchestration
test/e2e/permutation_test.go
Wires the "Permutation" describe block with tiers 1–10 in continue-on-failure mode; BeforeAll sets shared env and AfterAll writes permutation report and runs cleanup sweep.
Result recording and reporting
test/e2e/permutation_report_test.go
Implements concurrency-safe permResult and permRecorder to capture case outcomes with stdout/stderr hashing, exit codes, and timing; generates permutation-report.json and markdown-rendered permutation-report.md with per-tier summary tables, failure reasons, command args, env overrides, and truncated stderr.
Cleanup sweep and resource removal
test/e2e/permutation_cleanup_test.go
Implements permSweep to scan and delete test-created APISIX resources (routes, services, consumers, SSL, secrets, global-rules, stream-routes, protos, plugin-metadata) by prefix; includes per-resource list/delete helpers for both runtime and control-plane admin endpoints with detection of unsupported responses and support for flat/nested ID response shapes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Security Check ❌ Error Real API tokens stored unredacted in reports; io.ReadAll errors suppressed in cleanup paths; deferred cleanup errors swallowed. Add redact functions for --token/A7_TOKEN; fix io.ReadAll error handling; log cleanup failures instead of suppressing.
E2e Test Quality Review ⚠️ Warning Error handling violations: io.ReadAll errors ignored in cleanup_test.go; cleanup errors suppressed in permutation_test.go; os.Getwd error unhandled; tokens unredacted in reports. Handle io.ReadAll/os.Getwd errors explicitly; log cleanup failures in defers; implement redactArgs/redactEnvOverrides to redact tokens/API keys from reports.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a comprehensive e2e permutation/combination stability test suite for the a7 CLI.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/permutation-cli-suite

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

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/e2e/permutation_cleanup_test.go`:
- Line 90: Replace the ignored error from io.ReadAll(resp.Body) with proper
error handling: change occurrences like body, _ := io.ReadAll(resp.Body) to
capture the error (body, err := io.ReadAll(resp.Body)) and on err != nil fail
the test or propagate the error (e.g., t.Fatalf("reading response body failed:
%v", err)) so malformed/partial responses are surfaced; update all similar sites
(the instances at the noted lines) that read resp.Body to follow this pattern
and ensure resp.Body is still closed as currently done.

In `@test/e2e/permutation_matrix_test.go`:
- Around line 597-599: The artifactDir helper currently ignores os.Getwd()
errors; update the artifactDir function to return (string, error) instead of
just string, check the error from os.Getwd() and return a wrapped error (e.g.,
fmt.Errorf("getting working dir: %w", err)) when it fails, and only
construct/return filepath.Join(wd, "_artifacts") on success; then update all
callers of artifactDir (tests in this package) to handle the returned error
(fail the test or propagate) instead of assuming a valid path.

In `@test/e2e/permutation_report_test.go`:
- Around line 56-58: The permutation report is storing raw Args and EnvOverrides
which may include sensitive tokens; create and use a redaction helper (e.g.,
redactSensitive) to sanitize slices before assignment so Args:
redactSensitive(append([]string(nil), args...)) and EnvOverrides:
redactSensitive(append([]string(nil), envOverrides...)); redactSensitive should
detect patterns like "--token", "-token", "A7_TOKEN", "API_KEY", both forms
key=value and separate-arg values and replace the secret portion with a fixed
placeholder (e.g., "<redacted>"); apply the same redaction at the other
occurrences mentioned (around lines 184-187) so no raw tokens are stored or
printed.

In `@test/e2e/permutation_test.go`:
- Around line 103-106: The deferred anonymous function currently swallows errors
from spec.cleanup(id, parentID); change it to capture and handle the returned
error instead of discarding it — e.g., inside the defer replace "_ =
spec.cleanup(id, parentID)" with "if err := spec.cleanup(id, parentID); err !=
nil { t.Fatalf(\"cleanup failed: %v\", err) }" or use your test helper
(require.NoError/require.NoErrorf) to fail the test on cleanup errors; apply the
same fix for the other deferred cleanup sites that call spec.cleanup.
🪄 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: 3d1a77b3-e9f5-4d0d-b3e0-c23cc69e8b28

📥 Commits

Reviewing files that changed from the base of the PR and between e97dda6 and 3d8db61.

📒 Files selected for processing (6)
  • .gitignore
  • Makefile
  • test/e2e/permutation_cleanup_test.go
  • test/e2e/permutation_matrix_test.go
  • test/e2e/permutation_report_test.go
  • test/e2e/permutation_test.go

return nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
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 | ⚡ Quick win

Handle io.ReadAll errors in cleanup HTTP paths.

These reads currently ignore failures, which can hide malformed/partial responses and break unsupported-endpoint detection or delete diagnostics.

Proposed fix pattern
-body, _ := io.ReadAll(resp.Body)
+body, readErr := io.ReadAll(resp.Body)
+if readErr != nil {
+	return nil, fmt.Errorf("read %s response body: %w", path, readErr)
+}
-body, _ := io.ReadAll(resp.Body)
+body, readErr := io.ReadAll(resp.Body)
+if readErr != nil {
+	return fmt.Errorf("read DELETE %s response body: %w", withForce, readErr)
+}

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

Also applies to: 106-106, 184-184, 225-225

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/e2e/permutation_cleanup_test.go` at line 90, Replace the ignored error
from io.ReadAll(resp.Body) with proper error handling: change occurrences like
body, _ := io.ReadAll(resp.Body) to capture the error (body, err :=
io.ReadAll(resp.Body)) and on err != nil fail the test or propagate the error
(e.g., t.Fatalf("reading response body failed: %v", err)) so malformed/partial
responses are surfaced; update all similar sites (the instances at the noted
lines) that read resp.Body to follow this pattern and ensure resp.Body is still
closed as currently done.

Comment on lines +597 to +599
wd, _ := os.Getwd()
// test/e2e is the working dir under ginkgo; resolve to ./_artifacts inside.
return filepath.Join(wd, "_artifacts")
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 | ⚡ Quick win

Handle os.Getwd() failure when resolving artifact output path.

artifactDir() currently ignores the os.Getwd() error, which can silently produce an incorrect output location.

Proposed fix
func artifactDir() string {
-	wd, _ := os.Getwd()
+	wd, err := os.Getwd()
+	if err != nil {
+		return "_artifacts"
+	}
 	// test/e2e is the working dir under ginkgo; resolve to ./_artifacts inside.
 	return filepath.Join(wd, "_artifacts")
}

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

📝 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
wd, _ := os.Getwd()
// test/e2e is the working dir under ginkgo; resolve to ./_artifacts inside.
return filepath.Join(wd, "_artifacts")
wd, err := os.Getwd()
if err != nil {
return "_artifacts"
}
// test/e2e is the working dir under ginkgo; resolve to ./_artifacts inside.
return filepath.Join(wd, "_artifacts")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/e2e/permutation_matrix_test.go` around lines 597 - 599, The artifactDir
helper currently ignores os.Getwd() errors; update the artifactDir function to
return (string, error) instead of just string, check the error from os.Getwd()
and return a wrapped error (e.g., fmt.Errorf("getting working dir: %w", err))
when it fails, and only construct/return filepath.Join(wd, "_artifacts") on
success; then update all callers of artifactDir (tests in this package) to
handle the returned error (fail the test or propagate) instead of assuming a
valid path.

Comment on lines +56 to +58
Args: append([]string(nil), args...),
EnvOverrides: append([]string(nil), envOverrides...),
StdoutSHA256: sha256hex(stdout),
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 | ⚡ Quick win

Redact token-bearing args/env before storing or rendering permutation reports.

The recorder currently stores and prints raw command args/env overrides, which can include real --token and A7_TOKEN values from auth-tier cases.

Proposed fix
res := permResult{
  Tier:            tier,
  TierName:        tierName(tier),
  Name:            name,
- Args:            append([]string(nil), args...),
- EnvOverrides:    append([]string(nil), envOverrides...),
+ Args:            redactArgs(args),
+ EnvOverrides:    redactEnvOverrides(envOverrides),
  ...
}
+func redactArgs(args []string) []string {
+	out := append([]string(nil), args...)
+	for i := 0; i < len(out); i++ {
+		switch out[i] {
+		case "--token", "--api-key":
+			if i+1 < len(out) {
+				out[i+1] = "<redacted>"
+			}
+		}
+	}
+	return out
+}
+
+func redactEnvOverrides(env []string) []string {
+	out := make([]string, 0, len(env))
+	for _, kv := range env {
+		if strings.HasPrefix(kv, "A7_TOKEN=") || strings.HasPrefix(kv, "AUTHORIZATION=") || strings.HasPrefix(kv, "X-API-KEY=") || strings.HasPrefix(kv, "COOKIE=") {
+			k := strings.SplitN(kv, "=", 2)[0]
+			out = append(out, k+"=<redacted>")
+			continue
+		}
+		out = append(out, kv)
+	}
+	return out
+}

As per coding guidelines **/*.{js,ts,tsx,go}: Do not log, serialize, or return API keys, tokens, or authentication headers without redaction.

Also applies to: 184-187

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/e2e/permutation_report_test.go` around lines 56 - 58, The permutation
report is storing raw Args and EnvOverrides which may include sensitive tokens;
create and use a redaction helper (e.g., redactSensitive) to sanitize slices
before assignment so Args: redactSensitive(append([]string(nil), args...)) and
EnvOverrides: redactSensitive(append([]string(nil), envOverrides...));
redactSensitive should detect patterns like "--token", "-token", "A7_TOKEN",
"API_KEY", both forms key=value and separate-arg values and replace the secret
portion with a fixed placeholder (e.g., "<redacted>"); apply the same redaction
at the other occurrences mentioned (around lines 184-187) so no raw tokens are
stored or printed.

Comment on lines +103 to +106
defer func() {
if spec.cleanup != nil {
_ = spec.cleanup(id, parentID)
}
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 | ⚡ Quick win

Stop swallowing deferred cleanup errors in tier workflows.

These deferred deletes currently suppress errors. When cleanup fails, the suite can leak resources and make later cases fail nondeterministically.

Proposed fix pattern
defer func() {
	if spec.cleanup != nil {
-		_ = spec.cleanup(id, parentID)
+		if err := spec.cleanup(id, parentID); err != nil {
+			GinkgoWriter.Printf("cleanup failed for %s/%s: %v\n", spec.name, id, err)
+		}
	}
}()
-defer func() { _ = deleteGlobalRuleByID(pluginID) }()
+defer func() {
+	if err := deleteGlobalRuleByID(pluginID); err != nil {
+		GinkgoWriter.Printf("cleanup failed for global-rule/%s: %v\n", pluginID, err)
+	}
+}()
-defer func() { _ = deleteSecretByID(id) }()
+defer func() {
+	if err := deleteSecretByID(id); err != nil {
+		GinkgoWriter.Printf("cleanup failed for secret/%s: %v\n", id, err)
+	}
+}()

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

Also applies to: 291-291, 360-360

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/e2e/permutation_test.go` around lines 103 - 106, The deferred anonymous
function currently swallows errors from spec.cleanup(id, parentID); change it to
capture and handle the returned error instead of discarding it — e.g., inside
the defer replace "_ = spec.cleanup(id, parentID)" with "if err :=
spec.cleanup(id, parentID); err != nil { t.Fatalf(\"cleanup failed: %v\", err)
}" or use your test helper (require.NoError/require.NoErrorf) to fail the test
on cleanup errors; apply the same fix for the other deferred cleanup sites that
call spec.cleanup.

…gaps

The first CI run on this branch surfaced two problems:

1. The suite ran inside the regular `make test-e2e` target. That target is
   the existing CI gate; bringing 290+ permutation cases into it both
   inflates the run time and tied PR merge to the suite reporting clean —
   which it does not yet, because three real CLI inconsistencies are still
   open (fixed in PR #56 and PR #57). The permutation matrix is meant to be
   a manual triage tool, not a CI gate.

   Fix: tag the Describe with Label("permutation") and update test-e2e /
   test-e2e-full to skip it via --label-filter='!permutation'. The dedicated
   test-e2e-permutation target inverts the filter to include only the
   permutation specs. Net effect: regular CI no longer sees the suite;
   `make test-e2e-permutation` is unchanged.

2. The CI EE rejected `stream-route create` with `Error: API error
   (status 400): can not create a Stream Route to the HTTP Service`. Stream
   routes need an L4 service on that deployment; my local EE accepted them
   attached to an HTTP service. The walker then cascaded 7 failures
   (get / list / export / delete all error with "resource not found").

   Fix: detect known capability-gap stderr patterns after the create step
   and downgrade the rest of the walker to a single informational "skipped"
   record. Patterns cover stream-route on HTTP-only EEs plus the existing
   secret-provider / vault gaps already handled by local_stability.

The walker now also stops short on any create failure (capability gap or
otherwise) rather than producing cascade noise from "resource not found"
on every follow-up step.
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.

1 participant