@@ -2,6 +2,8 @@ package controllers
2
2
3
3
import (
4
4
"errors"
5
+ "fmt"
6
+ "strings"
5
7
"testing"
6
8
7
9
"github.com/google/go-cmp/cmp"
@@ -64,3 +66,177 @@ func TestSetStatusProgressing(t *testing.T) {
64
66
})
65
67
}
66
68
}
69
+
70
+ func TestTruncateMessage (t * testing.T ) {
71
+ tests := []struct {
72
+ name string
73
+ message string
74
+ expected string
75
+ }{
76
+ {
77
+ name : "short message unchanged" ,
78
+ message : "This is a short message" ,
79
+ expected : "This is a short message" ,
80
+ },
81
+ {
82
+ name : "empty message unchanged" ,
83
+ message : "" ,
84
+ expected : "" ,
85
+ },
86
+ {
87
+ name : "exact max length message unchanged" ,
88
+ message : strings .Repeat ("a" , maxConditionMessageLength ),
89
+ expected : strings .Repeat ("a" , maxConditionMessageLength ),
90
+ },
91
+ {
92
+ name : "message just over limit gets truncated" ,
93
+ message : strings .Repeat ("a" , maxConditionMessageLength + 1 ),
94
+ expected : strings .Repeat ("a" , maxConditionMessageLength - len (truncationSuffix )) + truncationSuffix ,
95
+ },
96
+ {
97
+ name : "very long message gets truncated" ,
98
+ message : strings .Repeat ("word " , 10000 ) + "finalword" ,
99
+ expected : strings .Repeat ("word " , 10000 )[:maxConditionMessageLength - len (truncationSuffix )] + truncationSuffix ,
100
+ },
101
+ }
102
+
103
+ for _ , tc := range tests {
104
+ t .Run (tc .name , func (t * testing.T ) {
105
+ result := truncateMessage (tc .message )
106
+ require .Equal (t , tc .expected , result )
107
+
108
+ // Verify the result is within the limit
109
+ require .LessOrEqual (t , len (result ), maxConditionMessageLength ,
110
+ "truncated message should not exceed max length" )
111
+
112
+ // If the original message was over the limit, verify truncation occurred
113
+ if len (tc .message ) > maxConditionMessageLength {
114
+ require .Contains (t , result , truncationSuffix ,
115
+ "long messages should contain truncation suffix" )
116
+ require .Less (t , len (result ), len (tc .message ),
117
+ "truncated message should be shorter than original" )
118
+ }
119
+ })
120
+ }
121
+ }
122
+
123
+ func TestSetStatusProgressingWithLongMessage (t * testing.T ) {
124
+ // Simulate a real ClusterExtension CRD upgrade safety check failure with many validation errors
125
+ longError := fmt .Sprintf ("validating CRD upgrade safety for ClusterExtension 'my-operator': %s" ,
126
+ strings .Repeat ("CRD \" myresources.example.com\" v1beta1->v1: field .spec.replicas changed from optional to required, field .spec.config.timeout type changed from string to integer, field .status.conditions[].observedGeneration removed\n " , 500 ))
127
+
128
+ ext := & ocv1.ClusterExtension {ObjectMeta : metav1.ObjectMeta {Name : "my-operator" }}
129
+ err := errors .New (longError )
130
+ setStatusProgressing (ext , err )
131
+
132
+ cond := meta .FindStatusCondition (ext .Status .Conditions , ocv1 .TypeProgressing )
133
+ require .NotNil (t , cond )
134
+ require .LessOrEqual (t , len (cond .Message ), maxConditionMessageLength )
135
+ require .Contains (t , cond .Message , truncationSuffix )
136
+ require .Contains (t , cond .Message , "validating CRD upgrade safety" )
137
+ }
138
+
139
+ func TestClusterExtensionDeprecationMessageTruncation (t * testing.T ) {
140
+ // Test truncation for ClusterExtension deprecation warnings with many deprecated APIs
141
+ ext := & ocv1.ClusterExtension {ObjectMeta : metav1.ObjectMeta {Name : "legacy-operator" }}
142
+
143
+ // Simulate many deprecation warnings that would overflow the message limit
144
+ deprecationMessages := []string {}
145
+ for i := 0 ; i < 1000 ; i ++ {
146
+ deprecationMessages = append (deprecationMessages , fmt .Sprintf ("API version 'v1beta1' of resource 'customresources%d.example.com' is deprecated, use 'v1' instead" , i ))
147
+ }
148
+
149
+ longDeprecationMsg := strings .Join (deprecationMessages , "; " )
150
+ setInstalledStatusConditionUnknown (ext , longDeprecationMsg )
151
+
152
+ cond := meta .FindStatusCondition (ext .Status .Conditions , ocv1 .TypeInstalled )
153
+ require .NotNil (t , cond )
154
+ require .LessOrEqual (t , len (cond .Message ), maxConditionMessageLength )
155
+ require .Contains (t , cond .Message , truncationSuffix , "deprecation messages should be truncated when too long" )
156
+ require .Contains (t , cond .Message , "API version" , "should preserve important deprecation context" )
157
+ }
158
+
159
+ func TestClusterExtensionInstallationFailureTruncation (t * testing.T ) {
160
+ // Test truncation for ClusterExtension installation failures with many bundle validation errors
161
+ installError := "failed to install ClusterExtension 'argocd-operator': bundle validation errors: " +
162
+ strings .Repeat ("resource 'deployments/argocd-server' missing required label 'app.kubernetes.io/name', resource 'services/argocd-server-metrics' has invalid port configuration, resource 'configmaps/argocd-cm' contains invalid YAML in data field 'application.yaml'\n " , 400 )
163
+
164
+ ext := & ocv1.ClusterExtension {ObjectMeta : metav1.ObjectMeta {Name : "argocd-operator" }}
165
+ setInstalledStatusConditionFailed (ext , installError )
166
+
167
+ cond := meta .FindStatusCondition (ext .Status .Conditions , ocv1 .TypeInstalled )
168
+ require .NotNil (t , cond )
169
+
170
+ // Verify message was truncated due to length
171
+ require .LessOrEqual (t , len (cond .Message ), maxConditionMessageLength )
172
+ require .Contains (t , cond .Message , truncationSuffix , "installation failure messages should be truncated when too long" )
173
+ require .Contains (t , cond .Message , "failed to install ClusterExtension" , "should preserve important context" )
174
+ require .Equal (t , metav1 .ConditionFalse , cond .Status )
175
+ require .Equal (t , ocv1 .ReasonFailed , cond .Reason )
176
+
177
+ // Verify original message was actually longer than the limit
178
+ require .Greater (t , len (installError ), maxConditionMessageLength , "test should use a message that exceeds the limit" )
179
+ }
180
+
181
+ func TestSetStatusConditionWrapper (t * testing.T ) {
182
+ tests := []struct {
183
+ name string
184
+ message string
185
+ expectedTruncated bool
186
+ }{
187
+ {
188
+ name : "short message not truncated" ,
189
+ message : "This is a short message" ,
190
+ expectedTruncated : false ,
191
+ },
192
+ {
193
+ name : "long message gets truncated" ,
194
+ message : strings .Repeat ("This is a very long message. " , 2000 ),
195
+ expectedTruncated : true ,
196
+ },
197
+ {
198
+ name : "message at exact limit not truncated" ,
199
+ message : strings .Repeat ("a" , maxConditionMessageLength ),
200
+ expectedTruncated : false ,
201
+ },
202
+ {
203
+ name : "message over limit gets truncated" ,
204
+ message : strings .Repeat ("a" , maxConditionMessageLength + 1 ),
205
+ expectedTruncated : true ,
206
+ },
207
+ }
208
+
209
+ for _ , tc := range tests {
210
+ t .Run (tc .name , func (t * testing.T ) {
211
+ var conditions []metav1.Condition
212
+
213
+ // Use our wrapper function
214
+ SetStatusCondition (& conditions , metav1.Condition {
215
+ Type : "TestCondition" ,
216
+ Status : metav1 .ConditionTrue ,
217
+ Reason : "Testing" ,
218
+ Message : tc .message ,
219
+ })
220
+
221
+ require .Len (t , conditions , 1 , "should have exactly one condition" )
222
+ cond := conditions [0 ]
223
+
224
+ // Verify message is within limits
225
+ require .LessOrEqual (t , len (cond .Message ), maxConditionMessageLength ,
226
+ "condition message should not exceed max length" )
227
+
228
+ // Check if truncation occurred as expected
229
+ if tc .expectedTruncated {
230
+ require .Contains (t , cond .Message , truncationSuffix ,
231
+ "long messages should contain truncation suffix" )
232
+ require .Less (t , len (cond .Message ), len (tc .message ),
233
+ "truncated message should be shorter than original" )
234
+ } else {
235
+ require .Equal (t , tc .message , cond .Message ,
236
+ "short messages should remain unchanged" )
237
+ require .NotContains (t , cond .Message , truncationSuffix ,
238
+ "short messages should not contain truncation suffix" )
239
+ }
240
+ })
241
+ }
242
+ }
0 commit comments