Skip to content

Add AI generated unit tests#2166

Open
eqrx wants to merge 1 commit intopackage-operator:mainfrom
eqrx:aitest
Open

Add AI generated unit tests#2166
eqrx wants to merge 1 commit intopackage-operator:mainfrom
eqrx:aitest

Conversation

@eqrx
Copy link
Copy Markdown
Member

@eqrx eqrx commented Sep 24, 2025

Company asked me to do use AI for code generation, this the result
(after heavily editing it).

@eqrx eqrx requested a review from a team as a code owner September 24, 2025 15:49
@eqrx eqrx force-pushed the aitest branch 2 times, most recently from 80ad3fe to 32254bd Compare September 25, 2025 09:03
Company asked me to do use AI for code generation, this the result
(after heavily editing it).
@codecov
Copy link
Copy Markdown

codecov bot commented Sep 25, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 63.33%. Comparing base (c443643) to head (46807f0).
⚠️ Report is 214 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2166      +/-   ##
==========================================
+ Coverage   62.27%   63.33%   +1.06%     
==========================================
  Files         230      230              
  Lines       13753    13753              
==========================================
+ Hits         8564     8711     +147     
+ Misses       4534     4386     -148     
- Partials      655      656       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@eqrx
Copy link
Copy Markdown
Member Author

eqrx commented Nov 14, 2025

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Nov 14, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Nov 14, 2025

Walkthrough

This pull request adds comprehensive test coverage across multiple packages and propagates context.Context parameters through function signatures in integration and validation components to enable cancellation and timeout control for command execution.

Changes

Cohort / File(s) Summary
Context propagation in kubectl integration tests
integration/kubectl-package/build_test.go, integration/kubectl-package/tree_test.go, integration/kubectl-package/update_test.go, integration/kubectl-package/validate_test.go, integration/kubectl-package/version_test.go
Updated test calls to pass context.Background() as first argument to testSubCommand, aligning with signature change in helpers.
Test helper signature update
integration/kubectl-package/helpers_test.go
Updated testSubCommand signature from testSubCommand(subcommand string) to testSubCommand(ctx context.Context, subcommand string). Changed command execution from exec.Command to exec.CommandContext for context-aware cancellation.
Project root context support
integration/kubectl-package/kubectl-package_suite_test.go
Updated projectRoot signature from projectRoot() to projectRoot(ctx context.Context) and switched to CommandContext for context-driven execution.
Image registry unit tests
cmd/build/definitions_test.go
Added comprehensive test suite for registry utilities covering default behavior, host extraction, namespace parsing, local registry paths, constants validation, and edge cases.
API manifest type tests
internal/apis/manifests/packagemanifest_types_test.go, internal/apis/manifests/packagemanifestlock_types_test.go, internal/apis/manifests/repository_types_test.go, internal/apis/manifests/repositoryentry_types_test.go
Added new test files validating PackageManifest, PackageManifestLock, Repository, and RepositoryEntry type structures, fields, metadata, and nested object hierarchies.
CLI and command option tests
internal/cli/options_test.go, internal/cmd/build_test.go, internal/cmd/cmd_test.go, internal/cmd/options_test.go, internal/cmd/resolver_test.go
Added test files covering CLI printer configuration, Build component initialization, client factory construction, command option propagation, and digest resolver behavior.
Core infrastructure tests
internal/constants/const_test.go, internal/controllers/config_test.go, internal/controllers/options_test.go
Added tests validating constant values, backoff configuration defaults and options, and option application for controller-related components.
Template validation context propagation
internal/packages/internal/packagevalidation/templatevalidation.go
Updated runDiff function signature from runDiff(fileA, labelA, fileB, labelB string) to runDiff(ctx context.Context, fileA, labelA, fileB, labelB string) and switched command execution to CommandContext for context-driven control.

Sequence Diagram(s)

sequenceDiagram
    participant Test as Test File
    participant Helper as testSubCommand Helper
    participant Exec as exec.CommandContext
    participant Plugin as Plugin Process

    Test->>Helper: testSubCommand(ctx, "subcommand")
    Helper->>Exec: CommandContext(ctx, pluginPath, args...)
    Exec->>Plugin: Launch with context
    Plugin-->>Exec: Execute command
    Exec-->>Helper: Return result
    Helper-->>Test: Return test case function
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Multiple test files with varied logic: Spread across API types, CLI options, controllers, and constants requires separate reasoning for each domain.
  • Context propagation pattern repetition: Seven files in integration/kubectl-package/ follow the same context addition pattern, reducing marginal review per file.
  • API type test coverage: Files like repositoryentry_types_test.go introduce complex nested structure validation requiring careful assertion verification.
  • New test infrastructure: Mock implementations in cmd_test.go and mock clock/loader in options_test.go require validation of test double correctness.
  • Specific areas requiring attention:
    • internal/apis/manifests/repositoryentry_types_test.go — complex constraint and platform logic assertions
    • internal/cmd/cmd_test.go — mock client implementations and factory wiring correctness
    • Context propagation through exec.CommandContext changes — verify proper context usage and no unintended side effects

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description is minimal and lacks the required template structure including Summary, Change Type, and Check List sections. Complete the pull request template with detailed summary of changes, specify change type (Docs/Test), and confirm all checklist items.
Docstring Coverage ⚠️ Warning Docstring coverage is 5.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Add AI generated unit tests' is generic and vague, using non-descriptive terms that don't convey meaningful information about the scope or nature of the changes. Specify which components or areas received unit tests (e.g., 'Add unit tests for manifests, CLI, and build utilities').
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Copy link
Copy Markdown
Contributor

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

🧹 Nitpick comments (7)
internal/constants/const_test.go (3)

13-64: Consider the value of testing constant strings.

While these tests verify that constants have expected values, they provide limited protection since any intentional change to a constant requires updating both the constant and the test. These tests are most useful for catching typos during initial development.


86-95: Remove redundant test.

This test duplicates coverage from TestStaticCacheOwner (line 79), which already verifies the UID. Testing the same value through ObjectMeta.UID doesn't add meaningful coverage.

Apply this diff to remove the redundant test:

-func TestStaticCacheOwner_ObjectMeta(t *testing.T) {
-	t.Parallel()
-
-	owner := StaticCacheOwner()
-
-	// Verify ObjectMeta structure
-	assert.Equal(t, metav1.ObjectMeta{
-		UID: "123-456",
-	}, owner.ObjectMeta)
-}
-

97-108: Remove redundant test.

This test duplicates coverage from TestStaticCacheOwner (line 78), which already verifies the return type using assert.IsType. The manual type assertion here doesn't add meaningful coverage.

Apply this diff to remove the redundant test:

-func TestStaticCacheOwner_ReturnType(t *testing.T) {
-	t.Parallel()
-
-	owner := StaticCacheOwner()
-
-	// Verify it's a pointer to ObjectDeployment
-	assert.NotNil(t, owner)
-
-	// Verify the underlying type
-	_, ok := interface{}(owner).(*corev1alpha1.ObjectDeployment)
-	assert.True(t, ok, "StaticCacheOwner should return *corev1alpha1.ObjectDeployment")
-}
cmd/build/definitions_test.go (1)

51-106: Remove unused shouldPanic field.

The test table includes a shouldPanic field and panic handling logic (lines 95-100), but no test case sets shouldPanic to true. This appears to be unused scaffolding.

Apply this diff to simplify the test:

 func TestImageRegistryHost(t *testing.T) {
 	tests := []struct {
 		name         string
 		registry     string
 		expectedHost string
-		shouldPanic  bool
 	}{
 		{
 			name:         "quay.io registry",
 			registry:     "quay.io/foobar",
 			expectedHost: "quay.io",
 		},
 		// ... other test cases ...
 	}
 
 	for _, tt := range tests {
 		test := tt
 		t.Run(test.name, func(t *testing.T) {
 			// Save original value for this subtest
 			originalValue, exists := os.LookupEnv(imageRegistryEnvvarName)
 			defer func() {
 				if exists {
 					require.NoError(t, os.Setenv(imageRegistryEnvvarName, originalValue))
 				} else {
 					require.NoError(t, os.Unsetenv(imageRegistryEnvvarName))
 				}
 			}()
 
 			require.NoError(t, os.Setenv(imageRegistryEnvvarName, test.registry))
 
-			if test.shouldPanic {
-				assert.Panics(t, func() {
-					imageRegistryHost()
-				})
-				return
-			}
-
 			result := imageRegistryHost()
 			assert.Equal(t, test.expectedHost, result)
 		})
 	}
 }
internal/cmd/options_test.go (1)

321-325: Consider making mockClock deterministic.

The mockClock.Now() method returns the current wall-clock time via metav1.Now(), which makes tests non-deterministic. While current tests only verify that the clock is set (not its value), this is a testing anti-pattern that could cause flaky tests if future tests need to verify time-dependent behavior.

Consider returning a fixed time:

 type mockClock struct{}
 
 func (m *mockClock) Now() metav1.Time {
-	return metav1.Now()
+	return metav1.Time{Time: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)}
 }
internal/controllers/config_test.go (2)

19-33: Avoid asserting pointer identity in TestBackoffConfig_Option.

assert.Equal(t, &initialBackoff, cfg.InitialBackoff) and the MaxBackoff equivalent require that Option stores the same variable address, not just the same value. That’s an implementation detail; many reasonable implementations would allocate their own time.Duration and still be correct.

Consider asserting on the values instead to keep the test focused on behavior rather than storage details:

-	assert.Equal(t, &initialBackoff, cfg.InitialBackoff)
-	assert.Equal(t, &maxBackoff, cfg.MaxBackoff)
+	if assert.NotNil(t, cfg.InitialBackoff) {
+		assert.Equal(t, initialBackoff, *cfg.InitialBackoff)
+	}
+	if assert.NotNil(t, cfg.MaxBackoff) {
+		assert.Equal(t, maxBackoff, *cfg.MaxBackoff)
+	}

96-112: GetBackoff default test is reasonable, minor redundancy only.

The test sensibly checks for non-nil and correct type for the constructed backoff. The second assert.NotNil after the comments is redundant but harmless; you can drop it if you want to reduce noise.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between c443643 and 46807f0.

📒 Files selected for processing (21)
  • cmd/build/definitions_test.go (1 hunks)
  • integration/kubectl-package/build_test.go (2 hunks)
  • integration/kubectl-package/helpers_test.go (2 hunks)
  • integration/kubectl-package/kubectl-package_suite_test.go (2 hunks)
  • integration/kubectl-package/tree_test.go (1 hunks)
  • integration/kubectl-package/update_test.go (1 hunks)
  • integration/kubectl-package/validate_test.go (1 hunks)
  • integration/kubectl-package/version_test.go (1 hunks)
  • internal/apis/manifests/packagemanifest_types_test.go (1 hunks)
  • internal/apis/manifests/packagemanifestlock_types_test.go (1 hunks)
  • internal/apis/manifests/repository_types_test.go (1 hunks)
  • internal/apis/manifests/repositoryentry_types_test.go (1 hunks)
  • internal/cli/options_test.go (1 hunks)
  • internal/cmd/build_test.go (1 hunks)
  • internal/cmd/cmd_test.go (1 hunks)
  • internal/cmd/options_test.go (1 hunks)
  • internal/cmd/resolver_test.go (1 hunks)
  • internal/constants/const_test.go (1 hunks)
  • internal/controllers/config_test.go (1 hunks)
  • internal/controllers/options_test.go (1 hunks)
  • internal/packages/internal/packagevalidation/templatevalidation.go (3 hunks)
🧰 Additional context used
🪛 golangci-lint (2.5.0)
internal/controllers/config_test.go

[warning] 1-1: var-naming: avoid meaningless package names

(revive)


[warning] 1-1: var-naming: avoid meaningless package names

(revive)

internal/cmd/build_test.go

[warning] 1-1: var-naming: avoid meaningless package names

(revive)


[warning] 1-1: var-naming: avoid meaningless package names

(revive)

internal/cmd/resolver_test.go

[warning] 1-1: var-naming: avoid meaningless package names

(revive)


[warning] 1-1: var-naming: avoid meaningless package names

(revive)

internal/apis/manifests/repositoryentry_types_test.go

[warning] 1-1: var-naming: avoid meaningless package names

(revive)


[warning] 1-1: var-naming: avoid meaningless package names

(revive)

internal/controllers/options_test.go

[warning] 1-1: var-naming: avoid meaningless package names

(revive)


[warning] 1-1: var-naming: avoid meaningless package names

(revive)

internal/apis/manifests/packagemanifest_types_test.go

[warning] 1-1: var-naming: avoid meaningless package names

(revive)


[warning] 1-1: var-naming: avoid meaningless package names

(revive)

cmd/build/definitions_test.go

[warning] 1-1: var-naming: avoid meaningless package names

(revive)


[warning] 1-1: var-naming: avoid meaningless package names

(revive)

internal/cmd/cmd_test.go

[warning] 1-1: var-naming: avoid meaningless package names

(revive)


[warning] 1-1: var-naming: avoid meaningless package names

(revive)

internal/cmd/options_test.go

[warning] 1-1: var-naming: avoid meaningless package names

(revive)


[warning] 1-1: var-naming: avoid meaningless package names

(revive)

internal/apis/manifests/repository_types_test.go

[warning] 1-1: var-naming: avoid meaningless package names

(revive)


[warning] 1-1: var-naming: avoid meaningless package names

(revive)

internal/apis/manifests/packagemanifestlock_types_test.go

[warning] 1-1: var-naming: avoid meaningless package names

(revive)


[warning] 1-1: var-naming: avoid meaningless package names

(revive)

internal/cli/options_test.go

[warning] 1-1: var-naming: avoid meaningless package names

(revive)


[warning] 1-1: var-naming: avoid meaningless package names

(revive)

internal/constants/const_test.go

[warning] 1-1: var-naming: avoid meaningless package names

(revive)


[warning] 1-1: var-naming: avoid meaningless package names

(revive)

🔇 Additional comments (41)
internal/packages/internal/packagevalidation/templatevalidation.go (3)

163-163: LGTM: Proper context propagation.

The context is correctly passed from runTestCase to runDiff, enabling cancellation and timeout control for the diff operation.


244-265: LGTM: Proper use of CommandContext.

The function correctly:

  • Adds context.Context as the first parameter following Go conventions
  • Uses exec.CommandContext to enable cancellation and timeout control
  • Maintains existing error handling logic

This change allows the diff command to be cancelled if the parent context times out or is cancelled, preventing resource leaks.


1-265: Note: PR title vs. file content.

The PR title mentions "Add AI generated unit tests," but this file contains validation logic improvements (context propagation) rather than test additions. If test files exist in other parts of this PR, they weren't included in this review batch.

The context propagation changes in this file are well-implemented and follow Go best practices.

internal/cli/options_test.go (6)

1-8: LGTM! Clean imports and package structure.

The package declaration and imports are well-organized. The static analysis warning about "meaningless package names" is a false positive—cli is a standard, descriptive name for command-line interface packages.


10-20: LGTM! Well-structured test.

The test correctly verifies that WithOut.ConfigurePrinter sets the output writer on the config. Good use of t.Parallel() for concurrent test execution.


22-32: LGTM! Consistent test pattern.

The test correctly verifies error writer configuration. The structure mirrors TestWithOut_ConfigurePrinter, maintaining consistency across the test suite.


34-49: LGTM! Good integration test.

This test properly verifies that WithOut and WithErr can be applied together without conflict, ensuring both output and error writers are configured independently.


51-60: LGTM! Important nil-handling coverage.

Good defensive test that verifies nil writers are handled correctly. This edge case coverage helps prevent potential nil pointer dereferences downstream.


62-71: LGTM! Completes nil-handling coverage.

Consistent with TestWithOut_NilWriter, this test ensures nil error writers are properly handled, completing the edge case coverage for both output streams.

integration/kubectl-package/version_test.go (1)

6-12: LGTM! Context properly threaded through test helper.

The addition of the context import and passing context.Background() to testSubCommand correctly aligns with the updated helper signature, enabling context-aware command execution.

integration/kubectl-package/validate_test.go (1)

6-13: LGTM! Consistent context integration.

The changes properly add context support to the validate subcommand tests, maintaining consistency with the broader test suite refactoring.

integration/kubectl-package/update_test.go (1)

6-13: LGTM! Context correctly propagated.

The update subcommand tests now support context-aware execution, consistent with the test suite's refactoring pattern.

integration/kubectl-package/tree_test.go (1)

6-13: LGTM! Context integration complete.

The tree subcommand tests now properly thread context through the test helper, enabling controlled command execution.

integration/kubectl-package/build_test.go (1)

6-15: LGTM! Build tests updated correctly.

The build subcommand tests now use context-aware execution, maintaining consistency with the overall test suite refactoring.

integration/kubectl-package/kubectl-package_suite_test.go (1)

50-50: LGTM! Context properly integrated into suite setup.

The projectRoot function signature is correctly updated to accept a context parameter, and the implementation properly uses CommandContext for the git command. The call site in BeforeSuite correctly passes context.Background().

Also applies to: 110-113

integration/kubectl-package/helpers_test.go (1)

6-6: LGTM! Core helper correctly refactored for context support.

The testSubCommand helper function signature is properly updated to accept context.Context, and the implementation correctly uses exec.CommandContext for command execution. This enables proper cancellation and timeout control for the kubectl-package plugin commands.

Note: The context is currently always context.Background() from callers, and the timeout is still controlled via tc.Timeout field (line 34). This is a reasonable approach—the tc.Timeout controls the Ginkgo session timeout, while the context enables future command-level cancellation if needed.

Also applies to: 28-31

internal/constants/const_test.go (1)

72-84: LGTM!

This test properly verifies the behavior of StaticCacheOwner(), including return type, UID value, and consistency across multiple invocations.

cmd/build/definitions_test.go (4)

12-48: LGTM!

The test correctly verifies both default and environment-driven behavior, with proper cleanup of environment variables using defer.


109-161: LGTM!

The test provides comprehensive coverage of namespace extraction, including edge cases like registries without namespaces and multi-level namespace paths.


164-210: LGTM!

The test correctly verifies that localRegistry() combines the registry namespace with the provided host port across different registry formats.


223-257: LGTM!

The edge case tests provide valuable additional coverage for registry host extraction with ports and simple hostnames.

internal/cmd/resolver_test.go (2)

9-30: LGTM!

The option handling tests correctly verify that options are applied and that multiple options override each other appropriately (last one wins).


71-108: LGTM!

Good use of table-driven tests to verify ConfigureResolveDigest behavior with both true and false values, and proper verification of secure defaults.

internal/cmd/build_test.go (4)

11-43: LGTM!

The tests correctly verify error message handling and Build construction with various option combinations.


45-86: LGTM!

Good test coverage for option application and default initialization, including verification that provided values are preserved and nil fields are populated with defaults.


88-104: LGTM!

Good verification that multiple options can be applied together and all fields are set correctly.


106-116: LGTM!

The test correctly verifies error handling for invalid paths. The error message check ensures the right code path is executed.

internal/cmd/options_test.go (1)

14-317: Verify necessity of nolint directive.

The test coverage for option configurators looks good overall. However, at line 315, the //nolint:gocritic comment appears unnecessary—the append pattern append(existingTags, newTags...) is idiomatic Go and shouldn't trigger legitimate linter warnings.

Consider removing the directive unless there's a specific linter configuration requiring it:

-	expectedTags := append(existingTags, newTags...) //nolint:gocritic
+	expectedTags := append(existingTags, newTags...)

Otherwise, the comprehensive coverage of Configure* methods effectively verifies the option wiring pattern used throughout the package.

internal/cmd/cmd_test.go (3)

15-45: LGTM!

Good verification of scheme creation with GVK recognition check, error constant value, and factory construction.


47-83: LGTM!

Well-structured table-driven test covering both successful client creation and error handling scenarios.


135-141: LGTM!

Simple but correct constructor validation.

internal/controllers/options_test.go (3)

88-112: Type-level tests for options look fine.

The compile-time interface checks and duration alias assertions are clear and low-risk; they document the intended option types nicely.


114-158: Integration tests cover key backoff option behaviors.

The integration tests with BackoffConfig.Default() exercise defaulting, single-option and both-option flows, and implicitly verify non-nil pointers via dereference. This is solid coverage.


160-179: Override behavior test is clear and effective.

TestOptions_OverrideValues clearly expresses that later options should win and uses pointer dereferences to ensure the config is actually populated.

internal/controllers/config_test.go (3)

11-17: Constants test is straightforward and helpful.

The constants check directly guards against accidental changes to default timings and is simple enough to maintain.


113-128: Custom-values GetBackoff test looks good.

This verifies that user-supplied backoff settings still yield a valid flowcontrol.Backoff instance without relying on unexported internals.


130-142: Chained operations scenario is well covered.

The chained OptionDefaultGetBackoff path is nicely exercised, and the assertions capture both config state and successful backoff construction.

internal/apis/manifests/repository_types_test.go (1)

10-61: Comprehensive Repository accessor coverage

Validating the promoted TypeMeta/ObjectMeta getters/setters (including the empty-state check) gives solid signal if future schema tweaks break these helpers. Nicely focused fixture.

internal/apis/manifests/packagemanifestlock_types_test.go (1)

10-209: Thorough lock Spec scenario coverage

Exercising full/empty/partial Specs for images and dependencies hits every accessor path we usually risk during schema edits—great defensive net around the CRD surface.

internal/apis/manifests/repositoryentry_types_test.go (1)

10-173: RepositoryEntry constraint matrix looks solid

Mixing full, empty, minimal, and complex constraint scenarios mirrors real manifests and will catch regressions in constraint wiring early.

internal/apis/manifests/packagemanifest_types_test.go (1)

14-293: Deep PackageManifest fixture

Covering scopes/phases, probes, filters, repos/deps, and the templating/test context in one place gives high confidence against refactors in this large type.

Comment thread internal/cmd/cmd_test.go
Comment on lines +98 to +133
func TestDefaultKubeClientFactory_GetKubeClient(t *testing.T) {
t.Parallel()

tests := []struct {
name string
configFactory RestConfigFactory
expectError bool
}{
{
name: "config factory error",
configFactory: &mockRestConfigFactory{err: errors.New("config error")},
expectError: true,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
scheme := runtime.NewScheme()
factory := NewDefaultKubeClientFactory(scheme, test.configFactory)

client, err := factory.GetKubeClient()

if test.expectError {
require.Error(t, err)
require.Nil(t, client)
} else if err != nil {
// Note: In real scenarios without mocking the underlying client.New,
// this would likely still error due to invalid config, but we're
// testing the factory behavior

require.Error(t, err)
}
})
}
}
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.

⚠️ Potential issue | 🟡 Minor

Clarify test logic for non-error case.

Lines 124-130 contain confusing test logic:

} else if err != nil {
    // Note: In real scenarios...
    require.Error(t, err)
}

When expectError is false, the test should verify success with require.NoError(err) and require.NotNil(client). The current else-if branch still requires an error when expectError is false, which doesn't align with the test's intent.

Consider fixing the logic:

 		if test.expectError {
 			require.Error(t, err)
 			require.Nil(t, client)
 		} else {
-			// Note: In real scenarios without mocking the underlying client.New,
-			// this would likely still error due to invalid config, but we're
-			// testing the factory behavior
-
-			require.Error(t, err)
+			require.NoError(t, err)
+			require.NotNil(t, client)
 		}

Or add a test case with expectError: false and a mock config that returns valid config.

🤖 Prompt for AI Agents
In internal/cmd/cmd_test.go around lines 98 to 133 the test logic is inverted
for the non-error case: the else-if branch still asserts an error when
expectError is false. Fix by replacing the else-if block with an else that
asserts success (require.NoError(t, err) and require.NotNil(t, client)), or
alternatively add a separate table test case with expectError: false and a mock
RestConfigFactory that returns a valid config and assert no error and non-nil
client.

Comment thread internal/cmd/cmd_test.go
Comment on lines +143 to +156
func TestDefaultWaiter_WaitForCondition(t *testing.T) {
t.Parallel()

mockClient := &Client{client: &mockClient{}}
scheme := runtime.NewScheme()
waiter := NewDefaultWaiter(mockClient, scheme)

require.NotNil(t, waiter)
require.NotNil(t, waiter.waiter)

// Test that the waiter has the expected timeout and interval
// This is more of a constructor test since the actual waiting
// would require complex mocking of the wait.Waiter
}
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.

⚠️ Potential issue | 🟡 Minor

Rename or remove misleading test.

TestDefaultWaiter_WaitForCondition is misleadingly named—it doesn't test any waiting behavior, as the comment acknowledges. This is essentially a duplicate of TestNewDefaultWaiter (lines 158-168).

Consider one of the following:

  1. Remove as duplicate:

    -func TestDefaultWaiter_WaitForCondition(t *testing.T) {
    -	t.Parallel()
    -
    -	mockClient := &Client{client: &mockClient{}}
    -	scheme := runtime.NewScheme()
    -	waiter := NewDefaultWaiter(mockClient, scheme)
    -
    -	require.NotNil(t, waiter)
    -	require.NotNil(t, waiter.waiter)
    -
    -	// Test that the waiter has the expected timeout and interval
    -	// This is more of a constructor test since the actual waiting
    -	// would require complex mocking of the wait.Waiter
    -}
    -
  2. Or rename to reflect actual behavior:

    -func TestDefaultWaiter_WaitForCondition(t *testing.T) {
    +func TestDefaultWaiter_Construction(t *testing.T) {
📝 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 TestDefaultWaiter_WaitForCondition(t *testing.T) {
t.Parallel()
mockClient := &Client{client: &mockClient{}}
scheme := runtime.NewScheme()
waiter := NewDefaultWaiter(mockClient, scheme)
require.NotNil(t, waiter)
require.NotNil(t, waiter.waiter)
// Test that the waiter has the expected timeout and interval
// This is more of a constructor test since the actual waiting
// would require complex mocking of the wait.Waiter
}
func TestNewDefaultWaiter(t *testing.T) {
// ... rest of the file
Suggested change
func TestDefaultWaiter_WaitForCondition(t *testing.T) {
t.Parallel()
mockClient := &Client{client: &mockClient{}}
scheme := runtime.NewScheme()
waiter := NewDefaultWaiter(mockClient, scheme)
require.NotNil(t, waiter)
require.NotNil(t, waiter.waiter)
// Test that the waiter has the expected timeout and interval
// This is more of a constructor test since the actual waiting
// would require complex mocking of the wait.Waiter
}
func TestDefaultWaiter_Construction(t *testing.T) {
t.Parallel()
mockClient := &Client{client: &mockClient{}}
scheme := runtime.NewScheme()
waiter := NewDefaultWaiter(mockClient, scheme)
require.NotNil(t, waiter)
require.NotNil(t, waiter.waiter)
// Test that the waiter has the expected timeout and interval
// This is more of a constructor test since the actual waiting
// would require complex mocking of the wait.Waiter
}
🤖 Prompt for AI Agents
In internal/cmd/cmd_test.go around lines 143 to 156, the test named
TestDefaultWaiter_WaitForCondition does not exercise any waiting behavior and
duplicates TestNewDefaultWaiter; either delete this test to remove the
duplicate, or rename and adjust it to reflect its true intent (e.g.,
TestNewDefaultWaiter_Constructor) and assert the constructed waiter's fields
(timeout/interval) or add proper expectations by exposing/mock the internal
wait.Waiter to verify waiting behavior; pick one approach and update the test
name and assertions accordingly so the test name matches what it actually
verifies.

Comment on lines +32 to +69
func TestDefaultDigestResolver_ResolveDigest_Insecure(t *testing.T) {
t.Parallel()

resolver := &defaultDigestResolver{}

// Test with invalid reference to ensure we get the expected error format
// without actually making network calls
_, err := resolver.ResolveDigest("invalid-reference", WithInsecure(true))

// We expect an error since we're using an invalid reference
assert.Error(t, err)
}

func TestDefaultDigestResolver_ResolveDigest_Secure(t *testing.T) {
t.Parallel()

resolver := &defaultDigestResolver{}

// Test with invalid reference to ensure we get the expected error format
// without actually making network calls
_, err := resolver.ResolveDigest("invalid-reference", WithInsecure(false))

// We expect an error since we're using an invalid reference
assert.Error(t, err)
}

func TestDefaultDigestResolver_ResolveDigest_NoOptions(t *testing.T) {
t.Parallel()

resolver := &defaultDigestResolver{}

// Test with invalid reference to ensure we get the expected error format
// without actually making network calls
_, err := resolver.ResolveDigest("invalid-reference")

// We expect an error since we're using an invalid reference
assert.Error(t, err)
}
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.

⚠️ Potential issue | 🟡 Minor

Strengthen test assertions to verify insecure option behavior.

These three tests are essentially identical—they only verify that an invalid reference produces an error, but they don't verify that the insecure option actually affects resolver behavior differently. Consider:

  1. Using a mock resolver to verify the insecure flag is passed correctly
  2. Testing valid but different scenarios for insecure vs. secure mode
  3. Verifying error message differences between modes

As written, these tests would pass even if the insecure option was completely ignored by the resolver.

Comment on lines +35 to +94
func TestBackoffConfig_Default(t *testing.T) {
t.Parallel()

tests := []struct {
name string
cfg BackoffConfig
expected BackoffConfig
}{
{
name: "empty config gets defaults",
cfg: BackoffConfig{},
expected: BackoffConfig{
InitialBackoff: func() *time.Duration { d := DefaultInitialBackoff; return &d }(),
MaxBackoff: func() *time.Duration { d := DefaultMaxBackoff; return &d }(),
},
},
{
name: "config with initial backoff preserves it",
cfg: BackoffConfig{
InitialBackoff: func() *time.Duration { d := 5 * time.Second; return &d }(),
},
expected: BackoffConfig{
InitialBackoff: func() *time.Duration { d := 5 * time.Second; return &d }(),
MaxBackoff: func() *time.Duration { d := DefaultMaxBackoff; return &d }(),
},
},
{
name: "config with max backoff preserves it",
cfg: BackoffConfig{
MaxBackoff: func() *time.Duration { d := 60 * time.Second; return &d }(),
},
expected: BackoffConfig{
InitialBackoff: func() *time.Duration { d := DefaultInitialBackoff; return &d }(),
MaxBackoff: func() *time.Duration { d := 60 * time.Second; return &d }(),
},
},
{
name: "config with both values preserves them",
cfg: BackoffConfig{
InitialBackoff: func() *time.Duration { d := 5 * time.Second; return &d }(),
MaxBackoff: func() *time.Duration { d := 60 * time.Second; return &d }(),
},
expected: BackoffConfig{
InitialBackoff: func() *time.Duration { d := 5 * time.Second; return &d }(),
MaxBackoff: func() *time.Duration { d := 60 * time.Second; return &d }(),
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
cfg := test.cfg
cfg.Default()

assert.Equal(t, *test.expected.InitialBackoff, *cfg.InitialBackoff)
assert.Equal(t, *test.expected.MaxBackoff, *cfg.MaxBackoff)
})
}
}
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.

⚠️ Potential issue | 🔴 Critical

Fix range variable capture in parallel TestBackoffConfig_Default subtests.

The table-driven Default tests use t.Run + t.Parallel() and capture the test loop variable, which can lead to all subtests observing the last case. Rebinding per-iteration avoids this.

-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
+	for _, test := range tests {
+		test := test // capture range variable for this subtest
+		t.Run(test.name, func(t *testing.T) {
 			t.Parallel()
 			cfg := test.cfg
 			cfg.Default()
📝 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 TestBackoffConfig_Default(t *testing.T) {
t.Parallel()
tests := []struct {
name string
cfg BackoffConfig
expected BackoffConfig
}{
{
name: "empty config gets defaults",
cfg: BackoffConfig{},
expected: BackoffConfig{
InitialBackoff: func() *time.Duration { d := DefaultInitialBackoff; return &d }(),
MaxBackoff: func() *time.Duration { d := DefaultMaxBackoff; return &d }(),
},
},
{
name: "config with initial backoff preserves it",
cfg: BackoffConfig{
InitialBackoff: func() *time.Duration { d := 5 * time.Second; return &d }(),
},
expected: BackoffConfig{
InitialBackoff: func() *time.Duration { d := 5 * time.Second; return &d }(),
MaxBackoff: func() *time.Duration { d := DefaultMaxBackoff; return &d }(),
},
},
{
name: "config with max backoff preserves it",
cfg: BackoffConfig{
MaxBackoff: func() *time.Duration { d := 60 * time.Second; return &d }(),
},
expected: BackoffConfig{
InitialBackoff: func() *time.Duration { d := DefaultInitialBackoff; return &d }(),
MaxBackoff: func() *time.Duration { d := 60 * time.Second; return &d }(),
},
},
{
name: "config with both values preserves them",
cfg: BackoffConfig{
InitialBackoff: func() *time.Duration { d := 5 * time.Second; return &d }(),
MaxBackoff: func() *time.Duration { d := 60 * time.Second; return &d }(),
},
expected: BackoffConfig{
InitialBackoff: func() *time.Duration { d := 5 * time.Second; return &d }(),
MaxBackoff: func() *time.Duration { d := 60 * time.Second; return &d }(),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
cfg := test.cfg
cfg.Default()
assert.Equal(t, *test.expected.InitialBackoff, *cfg.InitialBackoff)
assert.Equal(t, *test.expected.MaxBackoff, *cfg.MaxBackoff)
})
}
}
func TestBackoffConfig_Default(t *testing.T) {
t.Parallel()
tests := []struct {
name string
cfg BackoffConfig
expected BackoffConfig
}{
{
name: "empty config gets defaults",
cfg: BackoffConfig{},
expected: BackoffConfig{
InitialBackoff: func() *time.Duration { d := DefaultInitialBackoff; return &d }(),
MaxBackoff: func() *time.Duration { d := DefaultMaxBackoff; return &d }(),
},
},
{
name: "config with initial backoff preserves it",
cfg: BackoffConfig{
InitialBackoff: func() *time.Duration { d := 5 * time.Second; return &d }(),
},
expected: BackoffConfig{
InitialBackoff: func() *time.Duration { d := 5 * time.Second; return &d }(),
MaxBackoff: func() *time.Duration { d := DefaultMaxBackoff; return &d }(),
},
},
{
name: "config with max backoff preserves it",
cfg: BackoffConfig{
MaxBackoff: func() *time.Duration { d := 60 * time.Second; return &d }(),
},
expected: BackoffConfig{
InitialBackoff: func() *time.Duration { d := DefaultInitialBackoff; return &d }(),
MaxBackoff: func() *time.Duration { d := 60 * time.Second; return &d }(),
},
},
{
name: "config with both values preserves them",
cfg: BackoffConfig{
InitialBackoff: func() *time.Duration { d := 5 * time.Second; return &d }(),
MaxBackoff: func() *time.Duration { d := 60 * time.Second; return &d }(),
},
expected: BackoffConfig{
InitialBackoff: func() *time.Duration { d := 5 * time.Second; return &d }(),
MaxBackoff: func() *time.Duration { d := 60 * time.Second; return &d }(),
},
},
}
for _, test := range tests {
test := test // capture range variable for this subtest
t.Run(test.name, func(t *testing.T) {
t.Parallel()
cfg := test.cfg
cfg.Default()
assert.Equal(t, *test.expected.InitialBackoff, *cfg.InitialBackoff)
assert.Equal(t, *test.expected.MaxBackoff, *cfg.MaxBackoff)
})
}
}
🤖 Prompt for AI Agents
internal/controllers/config_test.go lines 35-94: the table-driven test launches
parallel subtests while using the loop variable `test`, which causes a race
where all subtests may see the final iteration; rebind the loop variable
per-iteration (e.g., `tc := test` or rename the loop variable in the range)
before calling t.Run and t.Parallel so each subtest captures its own copy;
ensure t.Parallel remains inside the subtest closure after the rebind.

Comment on lines +10 to +47
func TestWithInitialBackoff_ConfigureBackoff(t *testing.T) {
t.Parallel()

tests := []struct {
name string
duration time.Duration
}{
{
name: "5 seconds",
duration: 5 * time.Second,
},
{
name: "1 minute",
duration: 1 * time.Minute,
},
{
name: "30 seconds",
duration: 30 * time.Second,
},
{
name: "zero duration",
duration: 0,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
option := WithInitialBackoff(test.duration)
config := &BackoffConfig{}

option.ConfigureBackoff(config)

assert.NotNil(t, config.InitialBackoff)
assert.Equal(t, test.duration, *config.InitialBackoff)
})
}
}
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.

⚠️ Potential issue | 🔴 Critical

Fix range variable capture in parallel subtests (InitialBackoff).

Using test from the range loop inside a t.Run that calls t.Parallel() can cause all subtests to see the last table entry due to how Go reuses the loop variable. Rebind per-iteration before starting the subtest.

-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			t.Parallel()
-			option := WithInitialBackoff(test.duration)
+	for _, test := range tests {
+		test := test // capture range variable for this subtest
+		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
+			option := WithInitialBackoff(test.duration)
📝 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 TestWithInitialBackoff_ConfigureBackoff(t *testing.T) {
t.Parallel()
tests := []struct {
name string
duration time.Duration
}{
{
name: "5 seconds",
duration: 5 * time.Second,
},
{
name: "1 minute",
duration: 1 * time.Minute,
},
{
name: "30 seconds",
duration: 30 * time.Second,
},
{
name: "zero duration",
duration: 0,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
option := WithInitialBackoff(test.duration)
config := &BackoffConfig{}
option.ConfigureBackoff(config)
assert.NotNil(t, config.InitialBackoff)
assert.Equal(t, test.duration, *config.InitialBackoff)
})
}
}
func TestWithInitialBackoff_ConfigureBackoff(t *testing.T) {
t.Parallel()
tests := []struct {
name string
duration time.Duration
}{
{
name: "5 seconds",
duration: 5 * time.Second,
},
{
name: "1 minute",
duration: 1 * time.Minute,
},
{
name: "30 seconds",
duration: 30 * time.Second,
},
{
name: "zero duration",
duration: 0,
},
}
for _, test := range tests {
test := test // capture range variable for this subtest
t.Run(test.name, func(t *testing.T) {
t.Parallel()
option := WithInitialBackoff(test.duration)
config := &BackoffConfig{}
option.ConfigureBackoff(config)
assert.NotNil(t, config.InitialBackoff)
assert.Equal(t, test.duration, *config.InitialBackoff)
})
}
}
🤖 Prompt for AI Agents
In internal/controllers/options_test.go around lines 10 to 47, the table-driven
subtests call t.Run with t.Parallel() while using the loop variable `test`
directly, which causes a range variable capture bug; rebind the loop variable
per-iteration (e.g., `tc := test`) immediately inside the loop and use `tc` in
the t.Run closure (and in any nested goroutines) so each parallel subtest sees
its own copy of the test data.

Comment on lines +49 to +86
func TestWithMaxBackoff_ConfigureBackoff(t *testing.T) {
t.Parallel()

tests := []struct {
name string
duration time.Duration
}{
{
name: "5 minutes",
duration: 5 * time.Minute,
},
{
name: "10 minutes",
duration: 10 * time.Minute,
},
{
name: "1 hour",
duration: 1 * time.Hour,
},
{
name: "zero duration",
duration: 0,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
option := WithMaxBackoff(test.duration)
config := &BackoffConfig{}

option.ConfigureBackoff(config)

assert.NotNil(t, config.MaxBackoff)
assert.Equal(t, test.duration, *config.MaxBackoff)
})
}
}
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.

⚠️ Potential issue | 🔴 Critical

Fix range variable capture in parallel subtests (MaxBackoff).

Same issue here: the test loop variable is captured by parallel subtests, so cases can all run with the last table entry. Rebind inside the loop.

-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			t.Parallel()
-			option := WithMaxBackoff(test.duration)
+	for _, test := range tests {
+		test := test // capture range variable for this subtest
+		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
+			option := WithMaxBackoff(test.duration)
📝 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 TestWithMaxBackoff_ConfigureBackoff(t *testing.T) {
t.Parallel()
tests := []struct {
name string
duration time.Duration
}{
{
name: "5 minutes",
duration: 5 * time.Minute,
},
{
name: "10 minutes",
duration: 10 * time.Minute,
},
{
name: "1 hour",
duration: 1 * time.Hour,
},
{
name: "zero duration",
duration: 0,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
option := WithMaxBackoff(test.duration)
config := &BackoffConfig{}
option.ConfigureBackoff(config)
assert.NotNil(t, config.MaxBackoff)
assert.Equal(t, test.duration, *config.MaxBackoff)
})
}
}
func TestWithMaxBackoff_ConfigureBackoff(t *testing.T) {
t.Parallel()
tests := []struct {
name string
duration time.Duration
}{
{
name: "5 minutes",
duration: 5 * time.Minute,
},
{
name: "10 minutes",
duration: 10 * time.Minute,
},
{
name: "1 hour",
duration: 1 * time.Hour,
},
{
name: "zero duration",
duration: 0,
},
}
for _, test := range tests {
test := test // capture range variable for this subtest
t.Run(test.name, func(t *testing.T) {
t.Parallel()
option := WithMaxBackoff(test.duration)
config := &BackoffConfig{}
option.ConfigureBackoff(config)
assert.NotNil(t, config.MaxBackoff)
assert.Equal(t, test.duration, *config.MaxBackoff)
})
}
}
🤖 Prompt for AI Agents
internal/controllers/options_test.go around lines 49 to 86: the table-driven
tests spawn parallel subtests while capturing the loop variable `test`, causing
all subtests to potentially see the last table entry; rebind the loop variable
inside the for loop (e.g., assign `tt := test`) and use that new variable in the
t.Run closure and assertions so each subtest gets its own copy of the test case
before calling t.Parallel().

@erdii erdii added do-not-merge Must not be merged. and removed do-not-merge Must not be merged. labels Mar 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants