Skip to content

test(system): anchor index-addressed negative asserts to what they name - #3608

Merged
Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
fix/system-charts-container-anchors
Aug 14, 2026
Merged

test(system): anchor index-addressed negative asserts to what they name#3608
Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
fix/system-charts-container-anchors

Conversation

@lexfrei

@lexfrei Aleksei Sviridkin (lexfrei) commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Some negative assertions in these suites can't fail for the reason their test names give. contains and notContains compare list elements for equality, so --pinned-tenants= never equals a rendered --pinned-tenants=tenant-a=shard0, and {name: GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET} never matches an env entry carrying a valueFrom beside the name. Others forbid one rendered value under a name that claims the flag itself, so notContains --compatibility=cilium leaves the kilo default render free to carry --compatibility=flannel off the same values key.

Flag guards become notMatchRegex on args[*], which matches per element, so any value fails and so does the bare form. Mapping guards get any: true. flux-shard-operator gets an equal on the whole default arg list instead, and --shard-cpu-limit= is dropped because that list covers it. The vendored ouroboros templates keep per-flag guards: a whole-list pin there would go red on any upstream flag change, under a test name about one flag.

Several of these address containers[0] with nothing pinning that index, so a sidecar rendered ahead of the workload turns them green while the flag is live on the real container. Those get an equal on the container name first. A Cilium deny rule has no name, so that one pins the selector plus the rule and toPorts counts. Where a positive contains already sits on the same path it holds the index by itself and gets no extra anchor.

Assertions of the same shape remain in files this PR doesn't touch, including one in a package it does.

Tests

Every dead guard was confirmed dead first: make the forbidden content render, watch the old form stay green. compatibility: flannel in kilo's values, --gateway-api=true in place of the bare flag, --proxy-fqdn baked beside the composition flags, the OIDC client secret in the mode=None grafana render. Each one turns the new assertion red. Anchors checked the same way, with a sidecar rendered ahead of the workload.

hack/helm-unit-tests.sh is green.

Screenshots

None. This changes test files.

Downstream repositories

Every changed file is under a tests/ directory. No templates, no values, no rendered output.

Release note

NONE

@github-actions github-actions Bot added area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review size/L This PR changes 100-499 lines, ignoring generated files labels Aug 7, 2026
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a004c30d-cc75-4667-8a8f-e162921455b3

📥 Commits

Reviewing files that changed from the base of the PR and between d514930 and d73c102.

📒 Files selected for processing (4)
  • packages/system/flux-shard-operator/tests/workload_test.yaml
  • packages/system/kilo/tests/kilo_test.yaml
  • packages/system/ouroboros/tests/controller_test.yaml
  • packages/system/ouroboros/tests/cozystack_overrides_test.yaml
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/system/ouroboros/tests/cozystack_overrides_test.yaml
  • packages/system/kilo/tests/kilo_test.yaml
  • packages/system/flux-shard-operator/tests/workload_test.yaml
  • packages/system/ouroboros/tests/controller_test.yaml

📝 Walkthrough

Walkthrough

The PR strengthens seven Helm tests. Assertions now target named containers or scoped rules and validate complete argument lists or matching entries.

Changes

Test assertion strengthening

Layer / File(s) Summary
Rendered resource anchoring
packages/system/keycloak/tests/encryption_test.yaml, packages/system/monitoring/tests/oidc_test.yaml, packages/system/flux-shard-operator/tests/workload_test.yaml, packages/system/kilo/tests/kilo_test.yaml, packages/system/ouroboros/tests/controller_test.yaml
Tests verify the expected container name before checking configuration. OIDC environment checks match entries by name with any: true.
Complete argument validation
packages/system/flux-shard-operator/tests/workload_test.yaml, packages/system/ouroboros/tests/controller_test.yaml, packages/system/ouroboros/tests/cozystack_overrides_test.yaml
Tests validate ordered default arguments and reject unwanted flag variants by matching each argument.
Network policy scope validation
packages/system/cilium-networkpolicy/tests/networkpolicy_test.yaml
The test constrains the deny rule and toPorts entry before checking that port 7473 is absent from any matching port entry.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: ivanhunters

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: anchoring negative system test assertions to the containers or policy entries they target.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/system-charts-container-anchors

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@packages/system/ouroboros/tests/cozystack_overrides_test.yaml`:
- Around line 37-50: Add an assertion before the existing containers[0].args
check to verify that containers[0].name is proxy. Keep the current proxy
argument assertion unchanged and ensure the container identity is validated
first.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 13c9ba82-cdb3-4ff6-af69-ef9e43c9c880

📥 Commits

Reviewing files that changed from the base of the PR and between c6eb584 and d514930.

📒 Files selected for processing (7)
  • packages/system/cilium-networkpolicy/tests/networkpolicy_test.yaml
  • packages/system/flux-shard-operator/tests/workload_test.yaml
  • packages/system/keycloak/tests/encryption_test.yaml
  • packages/system/kilo/tests/kilo_test.yaml
  • packages/system/monitoring/tests/oidc_test.yaml
  • packages/system/ouroboros/tests/controller_test.yaml
  • packages/system/ouroboros/tests/cozystack_overrides_test.yaml

Comment on lines +37 to +50
- equal:
path: spec.template.spec.containers[0].args
content: --target-host=
value:
- proxy
- --listen-http=:8080
- --listen-https=:8443
- --listen-health=:8081
- --target-service-name=ingress-nginx-controller
- --target-service-namespace=cozy-ingress-nginx
- --target-http-port=80
- --target-https-port=443
- --dial-timeout=5s
- --ready-timeout=2s
- --shutdown-grace=30s

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Anchor the indexed container to proxy.

Line 37 checks containers[0].args without checking the container identity. If the template adds or reorders containers, this test can validate the wrong container or fail when the proxy arguments remain correct. Assert containers[0].name: proxy before this assertion.

Proposed fix
+      - equal:
+          path: spec.template.spec.containers[0].name
+          value: proxy
       - equal:
           path: spec.template.spec.containers[0].args
📝 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
- equal:
path: spec.template.spec.containers[0].args
content: --target-host=
value:
- proxy
- --listen-http=:8080
- --listen-https=:8443
- --listen-health=:8081
- --target-service-name=ingress-nginx-controller
- --target-service-namespace=cozy-ingress-nginx
- --target-http-port=80
- --target-https-port=443
- --dial-timeout=5s
- --ready-timeout=2s
- --shutdown-grace=30s
- equal:
path: spec.template.spec.containers[0].name
value: proxy
- equal:
path: spec.template.spec.containers[0].args
value:
- proxy
- --listen-http=:8080
- --listen-https=:8443
- --listen-health=:8081
- --target-service-name=ingress-nginx-controller
- --target-service-namespace=cozy-ingress-nginx
- --target-http-port=80
- --target-https-port=443
- --dial-timeout=5s
- --ready-timeout=2s
- --shutdown-grace=30s
🤖 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 `@packages/system/ouroboros/tests/cozystack_overrides_test.yaml` around lines
37 - 50, Add an assertion before the existing containers[0].args check to verify
that containers[0].name is proxy. Keep the current proxy argument assertion
unchanged and ensure the container identity is validated first.

@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the fix/system-charts-container-anchors branch 2 times, most recently from 61e4717 to 318b81f Compare August 9, 2026 22:15

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Overview

Reviewed statically against the actual chart templates and values.yaml defaults for all seven converted assertions, plus the helm-unittest contains/notContains validator source to confirm the any: true semantics the PR relies on.

Verdict: LGTM with non-blocking notes.

The core claim of this PR checks out: several of the "converted" assertions were genuinely dead before this change, not just theoretically weak.

  • packages/system/monitoring/templates/grafana/grafana.yaml always renders GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET and GF_PATHS_CUSTOM_INI with a valueFrom/value field beside name. The pre-PR bare-map notContains (no any: true) does an exact reflect.DeepEqual per the validator source, so it could never match those entries and was permanently green regardless of OIDC mode. The any: true addition (a genuine subset match, confirmed against contains_validator.go) is a real fix.
  • The --pinned-tenants=, --target-host=, and --shard-cpu-limit= prefix-style notContains/removed checks were indeed dead: Helm renders each flag as one joined string (--flag=value), so a bare-prefix element can never equal a rendered element. The equal-on-full-arg-list replacements in flux-shard-operator/tests/workload_test.yaml and ouroboros/tests/cozystack_overrides_test.yaml were checked element-by-element against the rendered defaults (templates/workload.yaml + package values.yaml, proxy-deployment.yaml + package values.yaml including the cozy-ingress-nginx namespace override) and match exactly, in order.
  • The four equal: containers[0].name anchors (proxy/kilo/grafana/controller) were checked against their respective single-container templates — all correct, and a reasonable defensive measure against a future sidecar shifting the index.
  • The Cilium lengthEqual + selector + any: true conversion was checked against templates/networkpolicy.yaml: port 7473 is indeed absent today, and the neighbouring 7946 entry does carry protocol: ANY beside port, which is exactly the shape the PR says would slip past a plain notContains — confirming the stated regression scenario is realistic, not hypothetical.

Non-blocking notes

  1. packages/system/ouroboros/tests/cozystack_overrides_test.yaml:37-40 — the "proxy dials the cozy-ingress-nginx Service..." test converts its dead notContains to a full-list equal on spec.template.spec.containers[0].args, but does not add a containers[0].name: proxy anchor first. Every other converted assertion in this PR gets that anchor — including the second test in this same file — and CodeRabbit flagged this exact gap with a concrete suggested diff that is still unaddressed at head. It isn't a silent-pass risk today (proxy-deployment.yaml renders exactly one container, and an equal on the full list fails loudly rather than silently if that ever changes), but it's inconsistent with the PR's own stated principle ("every index on a path gets anchored") and worth a one-line fix before merge for consistency and clearer failure messages down the line.
  2. Nice self-imposed scope discipline: explicitly not sweeping the whole tree, and explicitly not switching to name-based JSONPath filters (which would trade "wrong element" for "no element" — the same failure family). Both calls are correct and the reasoning is sound.

Closing

Solid, well-evidenced test-hygiene fix; the "dead assertion" diagnoses all held up against the actual templates. Addressing note #1 (or explicitly deferring it with a reason) would make the PR fully consistent with its own methodology, but it's not a blocker.

@myasnikovdaniil myasnikovdaniil left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reproduced both dead asserts. Pre-PR forms in networkpolicy_test.yaml and oidc_test.yaml stay green while the template regresses, the any: true forms go red. Nine converted guards mutation tested, all nine catch it. Anchors are not vacuous either, dropping a container name: makes equal error on unknown path instead of passing.

All six packages have a Makefile test: target so the runner picks them all up, none skipped. Local helm-unittest is v1.0.3 and CI installs unpinned which is v1.1.2 now, contains_validator.go is identical between them so results transfer.

value: kilo
- notContains:
path: spec.template.spec.containers[0].args
content: "--compatibility=cilium"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Test is named for --compatibility but the assert only forbids exact --compatibility=cilium, so it stays green when the default render carries the flag with another value. Set compatibility: flannel in values.yaml and the suite still passes 5/5.

Same family this PR is fixing, and the header comment at line 12 makes the same claim. Whole-args equal like you did in flux-shard-operator would close it, or just rename the test to name the cilium value.

value: controller
- notContains:
path: spec.template.spec.containers[0].args
content: --gateway-api

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This one is live only because the chart renders the flag bare. Every other flag in charts/ouroboros/templates/controller-deployment.yaml is --flag=value, so an upstream bump to --gateway-api=true quietly puts it back to the prefix-vs-element deadness you are fixing elsewhere. Not introduced here and the index anchor is correct, just worth a comment or the same equal treatment.

notContains passes whenever its path does not resolve, and also
whenever it resolves to something that simply lacks the forbidden
content. A different container at the same index qualifies, so these
guards go green the moment a sidecar is placed ahead of the workload
they were written for, while the flag or env var they forbid comes
back on the real container.

Assert the identity at that index first, in each affected case: the
container name for the Deployment-shaped ones, and for the Cilium
policy its selector, since a deny rule has no name. The selector
identifies that rule only while there is one rule to identify, so it
is paired with a length assertion, and with a second one on the
rule's toPorts, because that path carries two indexes and anchoring
the outer one alone leaves the inner free.

The anchor goes only where nothing else holds the index. A positive
contains on the same path fails on its own once a sidecar arrives
ahead of the workload, and an equal on the whole argument list fails
the same way, so neither carries a separate anchor. A negative does,
including the per-element form below: a wildcard resolving to the
sidecar's own arguments matches nothing and passes.

Several of the guards could not fail for the reason they name, and
the anchor alone would have left that hidden behind a comment saying
they were covered. contains and notContains compare elements for
equality, so a mapping is compared whole: `{name: GF_...}` never
matched an entry carrying a value beside the name, and `{port: 7473}`
would not match that port re-added with a protocol beside it, which
is how the neighbouring entry in the same list is written. Those get
`any: true`.

Equality against a rendered argument is the same failure worn
differently, and it arrives in three disguises. A prefix that never
equals an element: `--pinned-tenants=` never equalled
`--pinned-tenants=tenant-a=shard0`, and `--target-host=` never
equalled a rendered `--target-host=<value>`. One value out of many
under a name that claims the flag: `--compatibility=cilium` left the
kilo default render free to carry `--compatibility=flannel` off the
same values key, and the two `--proxy-fqdn` negatives forbade only
the FQDN the default cluster-domain produces. And a form that holds
by luck: `--gateway-api` still fails today only because that template
renders it bare, so an upstream switch to `--gateway-api=true` would
retire the guard in silence.

All of those become notMatchRegex over args[*], which matches the
flag name per element and fails on any value and on the bare form
alike. A length assertion would not have covered the prefix cases,
since an upstream bump that adds the flag while consolidating another
argument keeps the count. A wildcard that resolves to nothing still
passes, so the index anchors stay.

The whole argument list is a different instrument, answering what the
default invocation is rather than whether one flag is absent. It
fails on an argument added, removed or swapped instead of only on the
ones someone thought to enumerate, which is worth having where the
template is first-party and a failure is a deliberate change asking
to be read. flux-shard-operator gets it in `renders the default
Deployment shape`, replacing eleven individual contains that listed
exactly the same eleven elements; `--shard-cpu-limit=` was dead for
the prefix reason and is dropped rather than repaired, because that
list now covers it. The vendored ouroboros templates get no such pin:
upstream churn arrives there on its own schedule, and a whole-list
assertion would fail a test named for one flag for a reason that has
nothing to do with that flag.

Anchor every index on a path, not the outermost one. The Cilium
assertion reads spec.ingressDeny[0].toPorts[0].ports, and pinning the
rule list alone would still let the port return on a second toPorts
entry of the same rule.

Each guard was checked by rendering what it forbids onto the element
it names, and each anchor by rendering the shift it exists to catch.

The form is already used in this tree, in opensearch-operator and
dashboard. Assertions of the same shape remain in files this commit
does not touch, including one in a package it does, and stay out of
scope here.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Signed-off-by: Aleksei Sviridkin <3811295@gmail.com>
@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the fix/system-charts-container-anchors branch from 318b81f to d73c102 Compare August 12, 2026 12:50
@lexfrei Aleksei Sviridkin (lexfrei) added area/testing Issues or PRs related to testing (e2e, bats, unit tests) and removed area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review labels Aug 12, 2026

@myasnikovdaniil myasnikovdaniil left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Every assertion this rewrites was checked in both directions, and in 11 of 13 violation scenarios the old form was green on the exact violation its own name forbids.

Two are worth naming because they are the sharpest. A test called no --compatibility arg in the default render passed at base while --compatibility=cilium, the literal string it forbade, was rendered on the real kilo container, because a sidecar occupied containers[0]. And drops the stale frr-mode metrics port passed with port 7473 back in the deny rule, because an unrelated deny inserted ahead shifted the real rule to index 1. Both go red on this PR.

The two exceptions are cases where the old form happened to catch the one literal spelling it named, and the new form catches those too, so nothing regressed.

I also checked the design rule rather than trusting the commit message. notMatchRegex over containers[0].args[*] where containers[0] is a sidecar with no args passes silently, so the name anchors are load-bearing. And the converse: the four notContains left unanchored in the same keycloak file and the two unanchored notMatchRegex in ouroboros are each held by a positive contains on the same path, and every one of them reddens under the corresponding sidecar-ahead mutation in both forms. The selectivity is principled.

All six touched packages define a test: target and pass the make -n test probe, so none is in the silently-skipped class, and all six are green before and after, so no new assertion is vacuous on the unmutated chart.

Three things worth a line each.

The cilium length anchor at networkpolicy_test.yaml:98 couples a port-named test to the rule count. Appending an unrelated cluster-scoped deny to templates/networkpolicy.yaml, with 7473 nowhere in sight, reddens exactly drops the stale frr-mode metrics port and nothing else. That is defensible on your own criterion, since the template is first-party and adding a deny rule there is a deliberate change asking to be read. What is missing is a sentence saying so, so the next person to add a deny rule reads why a test about 7473 turned red instead of concluding the anchor is wrong.

Six assertions now rest on notMatchRegex plus wildcard-path semantics that CI does not pin. The plugin is installed unversioned at .github/workflows/pull-requests.yaml:209-210. On 1.0.3 a wildcard path makes notMatchRegex mean no element may match, which is what all six depend on. If a release changed that to some element does not match, all six become vacuous in precisely the way this PR exists to prevent and nothing in the repo would notice. Not yours, but this PR increases the blast radius of an unpinned tool and pinning it is cheap.

The flux-shard whole-list equal correctly catches --shard-cpu-limit=500m, which the deleted notContains never could, but the failure now surfaces in a test called renders the default Deployment shape, whose name carries no signal that the absence of a cpu-limit flag is deliberate. The comment at :20-27 explains the instrument and not that particular absence.

The red E2E is the node-join flake, 43 of 45 suites passed and the diff is helm-unittest YAML that E2E never loads.

@lexfrei
Aleksei Sviridkin (lexfrei) merged commit abd331b into main Aug 14, 2026
26 of 28 checks passed
@lexfrei
Aleksei Sviridkin (lexfrei) deleted the fix/system-charts-container-anchors branch August 14, 2026 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/testing Issues or PRs related to testing (e2e, bats, unit tests) size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants