Skip to content

test: e2e test for UIPlugin post-uninstall cascade cleanup (COO-1404)#1087

Open
DavidRajnoha wants to merge 1 commit into
rhobs:mainfrom
DavidRajnoha:COO-1404-test-pr-1084
Open

test: e2e test for UIPlugin post-uninstall cascade cleanup (COO-1404)#1087
DavidRajnoha wants to merge 1 commit into
rhobs:mainfrom
DavidRajnoha:COO-1404-test-pr-1084

Conversation

@DavidRajnoha
Copy link
Copy Markdown
Collaborator

Summary

  • Adds TestUIPluginUninstallCleanup e2e test that verifies deleting a UIPlugin CR after the operator has been uninstalled via OLM correctly cascade-deletes all child resources through Kubernetes garbage collection (OwnerReferences).
  • Per OLM design, uninstalling an operator (deleting CSV + Subscription) does not remove CRDs or CRs — the admin is expected to delete CRs manually. This test verifies that when they do, the OwnerReference chain works without the operator running.
  • Before the finalizer fix (COO-1404: fix: remove ui plugin finalizers in favor of k8s garbage collection #1084): UIPlugin CR gets stuck in Terminating forever (operator gone, can't remove finalizer). After the fix: CR deletes immediately, GC cascades to all children.

Also adds:

  • CapturePodLogs and FindPodByLabel helpers to the e2e framework for operator shutdown diagnostics
  • --postpone-restoration flag to run-e2e.sh for delayed Subscription restoration (allows manual cluster inspection)
  • --openshift flag support in run-e2e.sh for UIPlugin tests on OpenShift clusters

Dependencies

Test plan

  • Run TestUIPluginUninstallCleanup on OCP cluster with the finalizer fix — should pass (CR deletes immediately, children cascade-deleted)
  • Run TestUIPluginUninstallCleanup on OCP cluster without the finalizer fix — should fail at "Waiting for UIPlugin CR to be fully deleted" (finalizer blocks deletion)

Supersedes #1083.

Made with Cursor

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

Review Change Stack

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

This pull request adds OpenShift-conditional test infrastructure and a comprehensive e2e test for UIPlugin uninstall cleanup. The changes introduce a -postpone-restoration flag to the e2e test package, add OpenShift mode configuration to the e2e test script with a CSV patching helper function (enable_openshift), update OLM cleanup to delete subscriptions via label selector, and implement a complete end-to-end test (TestUIPluginUninstallCleanup) with multiple helper functions to validate cascade deletion and pod cleanup when UIPlugins are uninstalled via OLM.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding an e2e test for UIPlugin uninstall cascade cleanup, with the associated ticket reference (COO-1404).
Description check ✅ Passed The description is well-detailed and directly related to the changeset, explaining the test's purpose, dependencies, and test plan.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@openshift-ci openshift-ci Bot requested review from jgbernalp and zhuje May 11, 2026 11:24
@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented May 11, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: DavidRajnoha
Once this PR has been reviewed and has the lgtm label, please assign jgbernalp for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

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

🧹 Nitpick comments (4)
test/e2e/uiplugin_uninstall_test.go (2)

113-131: ⚡ Quick win

Saved Subscription loses metadata like labels/annotations and installPlanApproval may be insufficient on recreate.

The cleanup recreates a Subscription with only Name, Namespace, and Spec. OLM-related labels/annotations (e.g., operators.coreos.com/...) and any cluster-specific metadata will be lost. The recreated Subscription may behave slightly differently than the original (e.g., different label selectors used by delete_olm_subscription in run-e2e.sh). If post-test cleanup or subsequent test runs rely on those labels, this could cause flakes.

Consider capturing the full original (e.g., savedSub := sub.DeepCopy() and clearing ResourceVersion/UID) so the recreated resource matches the original modulo server-managed fields.

♻️ Suggested change
-		savedSub := &olmv1alpha1.Subscription{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      sub.Name,
-				Namespace: sub.Namespace,
-			},
-			Spec: sub.Spec.DeepCopy(),
-		}
+		savedSub := sub.DeepCopy()
+		savedSub.ResourceVersion = ""
+		savedSub.UID = ""
+		savedSub.Status = olmv1alpha1.SubscriptionStatus{}
🤖 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/uiplugin_uninstall_test.go` around lines 113 - 131, The cleanup
recreates savedSub with only Name/Namespace/Spec which loses labels/annotations
and other metadata; update the cleanup to store the full original Subscription
by doing savedSub := sub.DeepCopy() (or similar) before modifications, then
before calling f.K8sClient.Create clear server-managed fields like
ResourceVersion, UID, CreationTimestamp and ManagedFields but keep labels,
annotations and Spec (including installPlanApproval) intact so the recreated
Subscription matches the original; reference savedSub in the Cleanup closure and
call forceDeleteAllUIPlugins as before, then create the cleaned savedSub.

281-294: ⚡ Quick win

waitForResourceAbsent mutates the caller's obj and swallows non-NotFound errors.

Two subtle issues:

  1. obj is reused across poll iterations and is populated by Get on each success path — that's fine here because each call site passes a freshly allocated value, but it's a footgun for future callers.
  2. Errors other than NotFound (transient API server errors, RBAC, etc.) are silently treated as "not yet absent". A persistent error will only surface as a generic timeout via wait.Interrupted, making test failures harder to diagnose.

Consider logging the last error before failing:

♻️ Suggested change
 func waitForResourceAbsent(t *testing.T, name, namespace string, obj client.Object, timeout time.Duration) {
 	t.Helper()
 	key := client.ObjectKey{Name: name, Namespace: namespace}
+	var lastErr error
 	err := wait.PollUntilContextTimeout(context.Background(), 5*time.Second, timeout, true, func(ctx context.Context) (bool, error) {
-		if err := f.K8sClient.Get(ctx, key, obj); apierrors.IsNotFound(err) {
+		err := f.K8sClient.Get(ctx, key, obj)
+		if apierrors.IsNotFound(err) {
 			return true, nil
 		}
+		lastErr = err
 		return false, nil
 	})
 	if wait.Interrupted(err) {
 		kind := fmt.Sprintf("%T", obj)
-		t.Fatalf("%s %s/%s was not cleaned up (waited %v)", kind, namespace, name, timeout)
+		t.Fatalf("%s %s/%s was not cleaned up (waited %v, lastErr=%v)", kind, namespace, name, timeout, lastErr)
 	}
 }
🤖 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/uiplugin_uninstall_test.go` around lines 281 - 294,
waitForResourceAbsent currently reuses the caller-supplied obj and swallows all
non-NotFound errors; change it so each poll iteration allocates a fresh object
instance (e.g., via obj.DeepCopyObject() / type-specific new object) and pass
that to f.K8sClient.Get to avoid mutating the caller's obj, and surface
non-NotFound errors by returning them from the poll function (i.e., return
false, err) while recording the last error; on timeout (in
wait.Interrupted(err)) include the captured last error in the t.Fatalf message
so failures show the real API error instead of only a timeout.
test/run-e2e.sh (1)

185-187: ⚡ Quick win

Use an array for extra_args to handle empty/multi-value cases safely.

Static analysis flags $extra_args as unquoted (SC2086). A safer pattern is an array so empty stays empty and any future multi-arg case is preserved.

♻️ Suggested change
-	local extra_args=""
-	[[ -n "$POSTPONE_RESTORATION" ]] && extra_args="-postpone-restoration=$POSTPONE_RESTORATION"
-	go test -v -timeout $TEST_TIMEOUT ./test/e2e/... -run "$RUN_REGEX" -count 1 -args -operatorInstallNS="$OPERATORS_NS" $extra_args | tee "$LOGS_DIR/e2e.log" || ret=1
+	local -a extra_args=()
+	[[ -n "$POSTPONE_RESTORATION" ]] && extra_args+=("-postpone-restoration=$POSTPONE_RESTORATION")
+	go test -v -timeout "$TEST_TIMEOUT" ./test/e2e/... -run "$RUN_REGEX" -count 1 -args -operatorInstallNS="$OPERATORS_NS" "${extra_args[@]}" | tee "$LOGS_DIR/e2e.log" || ret=1
🤖 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/run-e2e.sh` around lines 185 - 187, The current use of a scalar
extra_args is unsafe; change it to an array (e.g., extra_args=()) and when
POSTPONE_RESTORATION is set append the argument as an array element (e.g.,
extra_args+=("--postpone-restoration=$POSTPONE_RESTORATION")), then invoke the
go test command passing the array with "${extra_args[@]}" alongside
-operatorInstallNS="$OPERATORS_NS" to avoid word-splitting and handle empty or
multi-value cases safely; update references to the extra_args variable in the go
test invocation accordingly.
pkg/operator/operator.go (1)

437-443: 💤 Low value

Use the manager's cached client where possible, or document why a fresh client is required.

Creating a new direct client at shutdown is fine if the manager's cache has already stopped serving reads, but it bypasses any client-side optimizations and may surprise future maintainers. Consider a brief comment explaining why o.manager.GetClient() is not used here (e.g., cache is torn down by the time ctx.Done() fires).

🤖 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 `@pkg/operator/operator.go` around lines 437 - 443, The shutdown code currently
creates a fresh direct client via client.New (assigned to directClient) which
bypasses the manager cache; either switch to using the manager's cached client
by calling o.manager.GetClient() for shutdown cleanup, or add a concise comment
above the client.New call explaining why a fresh client is required (e.g., the
manager cache may already be stopped when ctx.Done() fires), referencing
directClient, client.New, o.manager.GetClient(), and o.restConfig so future
maintainers understand the choice.
🤖 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 `@pkg/operator/operator.go`:
- Around line 396-400: The shutdown runnable added via
op.newShutdownCleanupRunnable() (registered with mgr.Add when
cfg.FeatureGates.OpenShift.Enabled is true) unconditionally calls
cleanupUIPluginsFromConsole on any shutdown; change it so it only runs when the
operator is actually being uninstalled: either remove the shutdown-time
deregistration entirely and rely on reconciliation/owner-ref GC, or modify
newShutdownCleanupRunnable()/cleanupUIPluginsFromConsole to first detect an
uninstall signal (for example, verify the UIPlugin CRD is no longer served via
discovery or the Console/operator CR shows a DeletionTimestamp or a specific
uninstall sentinel) and only proceed to patch the Console when that uninstall
condition is true; ensure the check is performed inside the runnable before
listing UIPlugin CRs so normal restarts (rolling updates, leader loss, SIGTERM)
do not remove Console entries.

---

Nitpick comments:
In `@pkg/operator/operator.go`:
- Around line 437-443: The shutdown code currently creates a fresh direct client
via client.New (assigned to directClient) which bypasses the manager cache;
either switch to using the manager's cached client by calling
o.manager.GetClient() for shutdown cleanup, or add a concise comment above the
client.New call explaining why a fresh client is required (e.g., the manager
cache may already be stopped when ctx.Done() fires), referencing directClient,
client.New, o.manager.GetClient(), and o.restConfig so future maintainers
understand the choice.

In `@test/e2e/uiplugin_uninstall_test.go`:
- Around line 113-131: The cleanup recreates savedSub with only
Name/Namespace/Spec which loses labels/annotations and other metadata; update
the cleanup to store the full original Subscription by doing savedSub :=
sub.DeepCopy() (or similar) before modifications, then before calling
f.K8sClient.Create clear server-managed fields like ResourceVersion, UID,
CreationTimestamp and ManagedFields but keep labels, annotations and Spec
(including installPlanApproval) intact so the recreated Subscription matches the
original; reference savedSub in the Cleanup closure and call
forceDeleteAllUIPlugins as before, then create the cleaned savedSub.
- Around line 281-294: waitForResourceAbsent currently reuses the
caller-supplied obj and swallows all non-NotFound errors; change it so each poll
iteration allocates a fresh object instance (e.g., via obj.DeepCopyObject() /
type-specific new object) and pass that to f.K8sClient.Get to avoid mutating the
caller's obj, and surface non-NotFound errors by returning them from the poll
function (i.e., return false, err) while recording the last error; on timeout
(in wait.Interrupted(err)) include the captured last error in the t.Fatalf
message so failures show the real API error instead of only a timeout.

In `@test/run-e2e.sh`:
- Around line 185-187: The current use of a scalar extra_args is unsafe; change
it to an array (e.g., extra_args=()) and when POSTPONE_RESTORATION is set append
the argument as an array element (e.g.,
extra_args+=("--postpone-restoration=$POSTPONE_RESTORATION")), then invoke the
go test command passing the array with "${extra_args[@]}" alongside
-operatorInstallNS="$OPERATORS_NS" to avoid word-splitting and handle empty or
multi-value cases safely; update references to the extra_args variable in the go
test invocation accordingly.
🪄 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: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 573f555c-d1e9-43a2-86be-22dfc1e3f0fc

📥 Commits

Reviewing files that changed from the base of the PR and between d0283f1 and cd2966d.

📒 Files selected for processing (8)
  • hack/dev-deploy.sh
  • pkg/controllers/uiplugin/controller.go
  • pkg/controllers/uiplugin/plugin_info_builder.go
  • pkg/operator/operator.go
  • test/e2e/framework/framework.go
  • test/e2e/main_test.go
  • test/e2e/uiplugin_uninstall_test.go
  • test/run-e2e.sh

Comment thread pkg/operator/operator.go
@DavidRajnoha DavidRajnoha force-pushed the COO-1404-test-pr-1084 branch 4 times, most recently from f8dce61 to cda37d2 Compare May 12, 2026 08:11
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: 2

🤖 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/aaa_uiplugin_uninstall_test.go`:
- Around line 259-261: The test only fails when both foundCSV and foundSub are
nil; change the guard to require both resources be present before proceeding by
failing if either is missing (i.e., replace the current conditional on foundCSV
and foundSub with a check that errors when foundCSV == nil || foundSub == nil)
so the uninstall cannot run with a partial lookup and leave the cluster in a bad
state; update the t.Fatal message to reflect that both Subscription and CSV are
required.
- Around line 271-276: The polling helpers waitForResourceGone and
waitForResourceAbsent currently swallow all non-NotFound errors by returning
(false, nil) in the wait.PollUntilContextTimeout callback; change the callback
so that when f.K8sClient.Get(ctx, key, obj) returns a non-nil error that is not
apierrors.IsNotFound(err) you return (false, err) to propagate the failure to
the poller (instead of retrying silently). Also adjust the callers of
waitForResourceGone / waitForResourceAbsent to check for and handle the returned
error from wait.PollUntilContextTimeout (or from the helper) rather than
treating everything as a timeout/interruption.
🪄 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: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: c4abc1c6-2091-4fbe-8236-42f2fc2232ab

📥 Commits

Reviewing files that changed from the base of the PR and between f8dce61 and fa2ba91.

📒 Files selected for processing (3)
  • test/e2e/aaa_uiplugin_uninstall_test.go
  • test/e2e/main_test.go
  • test/run-e2e.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/e2e/main_test.go
  • test/run-e2e.sh

Comment on lines +259 to +261
if foundCSV == nil && foundSub == nil {
t.Fatal("Could not find COO Subscription or CSV — operator may not be installed via OLM")
}
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

Require both Subscription and CSV before proceeding with uninstall.

This currently only fails when both are missing. If one lookup fails, the test can perform a partial uninstall and skip restoration setup, which can leave the cluster in a bad state for later tests.

Suggested fix
-	if foundCSV == nil && foundSub == nil {
-		t.Fatal("Could not find COO Subscription or CSV — operator may not be installed via OLM")
-	}
+	if foundCSV == nil || foundSub == nil {
+		t.Fatalf(
+			"Could not find both COO Subscription and CSV (foundCSV=%t, foundSub=%t) — refusing partial uninstall",
+			foundCSV != nil,
+			foundSub != nil,
+		)
+	}
📝 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
if foundCSV == nil && foundSub == nil {
t.Fatal("Could not find COO Subscription or CSV — operator may not be installed via OLM")
}
if foundCSV == nil || foundSub == nil {
t.Fatalf(
"Could not find both COO Subscription and CSV (foundCSV=%t, foundSub=%t) — refusing partial uninstall",
foundCSV != nil,
foundSub != nil,
)
}
🤖 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/aaa_uiplugin_uninstall_test.go` around lines 259 - 261, The test
only fails when both foundCSV and foundSub are nil; change the guard to require
both resources be present before proceeding by failing if either is missing
(i.e., replace the current conditional on foundCSV and foundSub with a check
that errors when foundCSV == nil || foundSub == nil) so the uninstall cannot run
with a partial lookup and leave the cluster in a bad state; update the t.Fatal
message to reflect that both Subscription and CSV are required.

Comment on lines +271 to +276
err := wait.PollUntilContextTimeout(context.Background(), 5*time.Second, timeout, true, func(ctx context.Context) (bool, error) {
if err := f.K8sClient.Get(ctx, key, obj); apierrors.IsNotFound(err) {
return true, nil
}
return false, nil
})
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

🧩 Analysis chain

🏁 Script executed:

cat -n test/e2e/aaa_uiplugin_uninstall_test.go | sed -n '260,300p'

Repository: rhobs/observability-operator

Length of output: 2053


Propagate non-NotFound API errors from polling helpers instead of silently swallowing them.

Both waitForResourceGone (line 272) and waitForResourceAbsent (line 285) check only for IsNotFound errors and ignore all other errors. Any transient API failure, RBAC denial, or network error triggers return false, nil, which causes the polling loop to retry silently until timeout. This masks real failures and misreports them as cleanup timeouts.

Restructure the error handling to return (false, err) for non-NotFound errors, allowing the polling mechanism to immediately surface the actual error:

if err := f.K8sClient.Get(ctx, key, obj); err != nil {
	if apierrors.IsNotFound(err) {
		return true, nil
	}
	return false, err
}
return false, nil

Then update the callers to handle the propagated error appropriately instead of relying solely on wait.Interrupted.

📝 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
err := wait.PollUntilContextTimeout(context.Background(), 5*time.Second, timeout, true, func(ctx context.Context) (bool, error) {
if err := f.K8sClient.Get(ctx, key, obj); apierrors.IsNotFound(err) {
return true, nil
}
return false, nil
})
err := wait.PollUntilContextTimeout(context.Background(), 5*time.Second, timeout, true, func(ctx context.Context) (bool, error) {
if err := f.K8sClient.Get(ctx, key, obj); err != nil {
if apierrors.IsNotFound(err) {
return true, nil
}
return false, err
}
return false, nil
})
🤖 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/aaa_uiplugin_uninstall_test.go` around lines 271 - 276, The polling
helpers waitForResourceGone and waitForResourceAbsent currently swallow all
non-NotFound errors by returning (false, nil) in the
wait.PollUntilContextTimeout callback; change the callback so that when
f.K8sClient.Get(ctx, key, obj) returns a non-nil error that is not
apierrors.IsNotFound(err) you return (false, err) to propagate the failure to
the poller (instead of retrying silently). Also adjust the callers of
waitForResourceGone / waitForResourceAbsent to check for and handle the returned
error from wait.PollUntilContextTimeout (or from the helper) rather than
treating everything as a timeout/interruption.

@DavidRajnoha DavidRajnoha force-pushed the COO-1404-test-pr-1084 branch 2 times, most recently from bf0060a to 853ad02 Compare May 15, 2026 07:18
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/uiplugin_uninstall_test.go (2)

275-277: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Require both Subscription and CSV before uninstall starts.

Line 275 currently fails only when both are missing; if either one is missing, the test can run a partial uninstall and leave cleanup/restoration in an inconsistent state. Fail when either resource is missing.

Proposed fix
-	if foundCSV == nil && foundSub == nil {
-		t.Fatal("Could not find COO Subscription or CSV — operator may not be installed via OLM")
-	}
+	if foundCSV == nil || foundSub == nil {
+		t.Fatalf(
+			"Could not find both COO Subscription and CSV (foundCSV=%t, foundSub=%t) — refusing partial uninstall",
+			foundCSV != nil,
+			foundSub != nil,
+		)
+	}

As per coding guidelines, "Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity."

🤖 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/uiplugin_uninstall_test.go` around lines 275 - 277, The test
currently only fails when both foundCSV and foundSub are nil; change the check
to fail if either resource is missing by testing if foundCSV == nil || foundSub
== nil and call t.Fatal with the same (or slightly updated) message so the
uninstall does not proceed when one of the required resources is absent; update
the conditional around foundCSV and foundSub and the t.Fatal invocation
accordingly.

287-305: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not swallow non-NotFound errors in polling helpers.

At Line 287-292 and Line 300-305, non-NotFound Get errors are retried silently, which masks real failures as timeouts. Propagate non-NotFound errors from the poll callback so the test fails with the real root cause.

Proposed fix
 func waitForResourceGone(t *testing.T, name, namespace string, obj client.Object, timeout time.Duration) bool {
 	t.Helper()
 	key := client.ObjectKey{Name: name, Namespace: namespace}
 	err := wait.PollUntilContextTimeout(context.Background(), 5*time.Second, timeout, true, func(ctx context.Context) (bool, error) {
-		if err := f.K8sClient.Get(ctx, key, obj); apierrors.IsNotFound(err) {
-			return true, nil
-		}
-		return false, nil
+		if err := f.K8sClient.Get(ctx, key, obj); err != nil {
+			if apierrors.IsNotFound(err) {
+				return true, nil
+			}
+			return false, err
+		}
+		return false, nil
 	})
-	return !wait.Interrupted(err)
+	if err != nil {
+		if wait.Interrupted(err) {
+			return false
+		}
+		t.Fatalf("error waiting for %T %s/%s deletion: %v", obj, namespace, name, err)
+	}
+	return true
 }
 
 func waitForResourceAbsent(t *testing.T, name, namespace string, obj client.Object, timeout time.Duration) {
 	t.Helper()
 	key := client.ObjectKey{Name: name, Namespace: namespace}
 	err := wait.PollUntilContextTimeout(context.Background(), 5*time.Second, timeout, true, func(ctx context.Context) (bool, error) {
-		if err := f.K8sClient.Get(ctx, key, obj); apierrors.IsNotFound(err) {
-			return true, nil
-		}
-		return false, nil
+		if err := f.K8sClient.Get(ctx, key, obj); err != nil {
+			if apierrors.IsNotFound(err) {
+				return true, nil
+			}
+			return false, err
+		}
+		return false, nil
 	})
-	if wait.Interrupted(err) {
+	if err != nil {
+		if wait.Interrupted(err) {
 		kind := fmt.Sprintf("%T", obj)
 		t.Fatalf("%s %s/%s was not cleaned up (waited %v)", kind, namespace, name, timeout)
+		}
+		t.Fatalf("error waiting for %T %s/%s cleanup: %v", obj, namespace, name, err)
 	}
 }

As per coding guidelines, "Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity."

🤖 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/uiplugin_uninstall_test.go` around lines 287 - 305, Both polling
helpers currently swallow non-NotFound errors from f.K8sClient.Get and retry
until timeout; change the poll callback in both the anonymous function inside
wait.PollUntilContextTimeout (used in the first helper returning bool and in
waitForResourceAbsent) to propagate any non-nil, non-NotFound error by returning
(false, err) instead of (false, nil), so that the wait fails fast with the real
error; locate the callbacks around the client.Get call and update the
conditional logic that currently only checks apierrors.IsNotFound(err) to handle
err != nil && !apierrors.IsNotFound(err) -> return false, err.
🤖 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/uiplugin_uninstall_test.go`:
- Around line 344-349: The polling callbacks passed to
wait.PollUntilContextTimeout suppress API errors by returning (false, nil) when
f.K8sClient.List fails; update those callbacks (the ones creating remaining
uiv1.UIPluginList and similar blocks) to propagate the actual error by returning
(false, err) instead of (false, nil), so change the anonymous func passed to
wait.PollUntilContextTimeout that calls f.K8sClient.List to return false, err on
List failures; apply the same fix to the other two polling callbacks that call
f.K8sClient.List.

---

Duplicate comments:
In `@test/e2e/uiplugin_uninstall_test.go`:
- Around line 275-277: The test currently only fails when both foundCSV and
foundSub are nil; change the check to fail if either resource is missing by
testing if foundCSV == nil || foundSub == nil and call t.Fatal with the same (or
slightly updated) message so the uninstall does not proceed when one of the
required resources is absent; update the conditional around foundCSV and
foundSub and the t.Fatal invocation accordingly.
- Around line 287-305: Both polling helpers currently swallow non-NotFound
errors from f.K8sClient.Get and retry until timeout; change the poll callback in
both the anonymous function inside wait.PollUntilContextTimeout (used in the
first helper returning bool and in waitForResourceAbsent) to propagate any
non-nil, non-NotFound error by returning (false, err) instead of (false, nil),
so that the wait fails fast with the real error; locate the callbacks around the
client.Get call and update the conditional logic that currently only checks
apierrors.IsNotFound(err) to handle err != nil && !apierrors.IsNotFound(err) ->
return false, err.
🪄 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: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 84cdeee6-a993-49ee-b210-c7e4b4ebfd88

📥 Commits

Reviewing files that changed from the base of the PR and between bf0060a and 853ad02.

📒 Files selected for processing (3)
  • test/e2e/main_test.go
  • test/e2e/uiplugin_uninstall_test.go
  • test/run-e2e.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/e2e/main_test.go
  • test/run-e2e.sh

Comment on lines +344 to +349
err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 2*time.Minute, true, func(ctx context.Context) (bool, error) {
var remaining uiv1.UIPluginList
if err := f.K8sClient.List(ctx, &remaining); err != nil {
return false, nil
}
return len(remaining.Items) == 0, nil
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

Propagate API list errors in remaining polling loops as well.

Line 344-349, Line 366-373, and Line 396-398 also suppress client List errors (return false, nil). This causes RBAC/API/network issues to surface as misleading timeout failures. Return false, err for non-retryable errors in these callbacks too.

As per coding guidelines, "Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity."

Also applies to: 366-373, 396-398

🤖 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/uiplugin_uninstall_test.go` around lines 344 - 349, The polling
callbacks passed to wait.PollUntilContextTimeout suppress API errors by
returning (false, nil) when f.K8sClient.List fails; update those callbacks (the
ones creating remaining uiv1.UIPluginList and similar blocks) to propagate the
actual error by returning (false, err) instead of (false, nil), so change the
anonymous func passed to wait.PollUntilContextTimeout that calls
f.K8sClient.List to return false, err on List failures; apply the same fix to
the other two polling callbacks that call f.K8sClient.List.

@DavidRajnoha DavidRajnoha force-pushed the COO-1404-test-pr-1084 branch from 853ad02 to 6ef3361 Compare May 15, 2026 09:58
…404)

Co-authored-by: Cursor <cursoragent@cursor.com>
@DavidRajnoha DavidRajnoha force-pushed the COO-1404-test-pr-1084 branch from 6ef3361 to 63998b8 Compare May 15, 2026 10:12
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