Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -752,14 +752,7 @@ func (r *HostedClusterReconciler) reconcile(ctx context.Context, req ctrl.Reques
// So consumers e.g. UI can categorize as good (True) / bad (False).
if conditionType == hyperv1.ClusterVersionSucceeding {
hcCVOCondition.Type = string(hyperv1.ClusterVersionSucceeding)
var status metav1.ConditionStatus
switch hcpCVOConditions[conditionType].Status {
case metav1.ConditionTrue:
status = metav1.ConditionFalse
case metav1.ConditionFalse:
status = metav1.ConditionTrue
}
hcCVOCondition.Status = status
hcCVOCondition.Status = invertConditionStatus(hcpCVOConditions[conditionType].Status)
}
}

Expand Down Expand Up @@ -3318,6 +3311,17 @@ func reconcileCAPIManagerClusterRoleBinding(binding *rbacv1.ClusterRoleBinding,
return nil
}

func invertConditionStatus(s metav1.ConditionStatus) metav1.ConditionStatus {
switch s {
case metav1.ConditionTrue:
return metav1.ConditionFalse
case metav1.ConditionFalse:
return metav1.ConditionTrue
default:
return metav1.ConditionUnknown
}
}

// computeClusterVersionStatus determines the ClusterVersionStatus of the
// given HostedCluster and returns it.
func computeClusterVersionStatus(clock clock.WithTickerAndDelayedExecution, hcluster *hyperv1.HostedCluster, hcp *hyperv1.HostedControlPlane) *hyperv1.ClusterVersionStatus {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3995,6 +3995,47 @@ func TestIsProgressing(t *testing.T) {
}
}

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

testCases := []struct {
name string
input metav1.ConditionStatus
expectedStatus metav1.ConditionStatus
}{
{
name: "When status is True it should invert to False",
input: metav1.ConditionTrue,
expectedStatus: metav1.ConditionFalse,
},
{
name: "When status is False it should invert to True",
input: metav1.ConditionFalse,
expectedStatus: metav1.ConditionTrue,
},
{
name: "When status is Unknown it should produce Unknown",
input: metav1.ConditionUnknown,
expectedStatus: metav1.ConditionUnknown,
},
{
name: "When status is empty string it should produce Unknown",
input: metav1.ConditionStatus(""),
expectedStatus: metav1.ConditionUnknown,
},
Comment on lines +4016 to +4025

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

Cover unexpected statuses beyond ConditionUnknown.

The new fallback handles every status other than True and False, but the table only tests the named Unknown constant. Add a case such as metav1.ConditionStatus("Unexpected") and expect metav1.ConditionUnknown.

As per coding guidelines, unit test any code changes and additions.

🤖 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
`@hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go`
around lines 4016 - 4020, Add a table-driven test case alongside the existing
ClusterVersionFailing status cases using metav1.ConditionStatus("Unexpected") as
hcpStatus and metav1.ConditionUnknown as expectedStatus, covering fallback
statuses beyond the named ConditionUnknown constant.

Source: Coding guidelines

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggestion: Consider adding a fourth test case for hcpStatus: "" (empty string). That's the exact zero-value that triggered the original production bug, and it would also hit the default branch:

{
	name:           "When ClusterVersionFailing has empty status it should produce ClusterVersionSucceeding Unknown",
	hcpStatus:      metav1.ConditionStatus(""),
	expectedStatus: metav1.ConditionUnknown,
},

}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
g := NewWithT(t)
Comment on lines +4029 to +4031

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggestion: The subtests should call t.Parallel() — other subtests in this file do (e.g., TestComputeAWSDefaultSGDeletedCondition). Since these cases are independent:

t.Run(tc.name, func(t *testing.T) {
	t.Parallel()
	g := NewWithT(t)

result := invertConditionStatus(tc.input)
g.Expect(result).To(Equal(tc.expectedStatus))
g.Expect(result).ToNot(BeEmpty(), "Status must not be empty string — API server rejects it")
})
}
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,14 +434,7 @@ func (r *HostedClusterReconciler) reconcileLegacy(ctx context.Context, req ctrl.
// So consumers e.g. UI can categorize as good (True) / bad (False).
if conditionType == hyperv1.ClusterVersionSucceeding {
hcCVOCondition.Type = string(hyperv1.ClusterVersionSucceeding)
var status metav1.ConditionStatus
switch hcpCVOConditions[conditionType].Status {
case metav1.ConditionTrue:
status = metav1.ConditionFalse
case metav1.ConditionFalse:
status = metav1.ConditionTrue
}
hcCVOCondition.Status = status
hcCVOCondition.Status = invertConditionStatus(hcpCVOConditions[conditionType].Status)
}
}

Expand Down