@@ -98,9 +98,11 @@ var validateKyvernoPoliciesCmd = &cobra.Command{
9898
9999 displayValidationResults (result , []kyverno.TestResource {testResource })
100100 if ! determineActualValidationResult (result , []kyverno.TestResource {testResource }) {
101+ fmt .Println ("❌ Kyverno policy validation failed." )
101102 os .Exit (1 )
102103 }
103- fmt .Println ("Kyverno policy validated successfully." )
104+ fmt .Println ("✅ Kyverno policy validated successfully." )
105+ return
104106 }
105107
106108 if kyvernoPolicyDir != "" {
@@ -128,20 +130,20 @@ var validateKyvernoPoliciesCmd = &cobra.Command{
128130 }
129131
130132 if len (policiesToValidate ) == 0 {
131- fmt .Println ("No policies to validate" )
132- return
133+ fmt .Println ("❌ No policies to validate" )
134+ os . Exit ( 1 )
133135 }
134136
135137 // Validate each policy
136138 allValid := true
137139 for _ , policyWithTestCases := range policiesToValidate {
138- fmt .Printf ( "Validating policy: %s" , policyWithTestCases . Policy . Name )
139-
140+ fmt .Println ( " \n --------------------------------" )
141+ fmt . Printf ( "🔍 Validating policy: %s \n " , policyWithTestCases . Policy . Name )
140142 result , err := kyverno .ValidateKyvernoPolicy (
141143 client , org , policyWithTestCases .Policy , policyWithTestCases .TestCases , true )
142144 if err != nil {
143- fmt .Printf ("Unable to validate policy %s: %v" , policyWithTestCases .Policy .Name , err )
144145 allValid = false
146+ fmt .Printf ("❌ Unable to validate policy %s: %v\n " , policyWithTestCases .Policy .Name , err )
145147 continue
146148 }
147149
@@ -152,10 +154,12 @@ var validateKyvernoPoliciesCmd = &cobra.Command{
152154 }
153155
154156 if ! allValid {
157+ fmt .Println ("\n --------------------------------" )
158+ fmt .Println ("❌ Some Kyverno policies validation failed. Please check the output for details." )
155159 os .Exit (1 )
156160 }
157-
158- fmt . Println ( "All Kyverno policies validated successfully!" )
161+ fmt . Println ( "✅ All Kyverno policies validated successfully!" )
162+ return
159163 }
160164 },
161165}
@@ -188,76 +192,155 @@ func displayValidationResults(result *kyverno.ValidationResult, testCases []kyve
188192 // Determine if validation actually passed based on test case results
189193 actualValid := determineActualValidationResult (result , testCases )
190194
191- if actualValid {
192- fmt .Printf ("✅ Policy validation: PASSED\n " )
193- } else {
194- fmt .Printf ("❌ Policy validation: FAILED\n " )
195+ // Display test case results with clear messaging
196+ if len (result .TestResults ) > 0 {
197+ fmt .Printf ("\n 📋 Test case results:\n " )
198+ for _ , testResult := range result .TestResults {
199+ passed := testResult .Passed
200+
201+ // Display the result with test case name prominently
202+ if passed {
203+ fmt .Printf (" ✅ %s (%s)\n " , testResult .TestCaseName , testResult .FileName )
204+ } else {
205+ fmt .Printf (" ❌ %s (%s)\n " , testResult .TestCaseName , testResult .FileName )
206+ }
207+ }
208+ }
209+
210+ // If TestResults are empty but we have test cases, show them with fallback info
211+ if len (result .TestResults ) == 0 && len (testCases ) > 0 {
212+ fmt .Printf ("\n 📋 Test cases:\n " )
213+
214+ // Check if we have mixed test cases (both success and failure)
215+ successCount := 0
216+ failureCount := 0
217+ for _ , tc := range testCases {
218+ switch tc .ExpectedOutcome {
219+ case "success" :
220+ successCount ++
221+ case "failure" :
222+ failureCount ++
223+ default :
224+ continue
225+ }
226+ }
227+ hasMixedCases := successCount > 0 && failureCount > 0
228+
229+ for _ , testCase := range testCases {
230+ var passed bool
231+ if hasMixedCases {
232+ passed = actualValid
233+ } else {
234+ switch testCase .ExpectedOutcome {
235+ case "success" :
236+ passed = len (result .Errors ) == 0
237+ case "failure" :
238+ passed = len (result .Errors ) > 0
239+ default :
240+ passed = actualValid
241+ }
242+ }
243+
244+ if passed {
245+ fmt .Printf (" ✅ %s (%s)\n " , testCase .TestCaseName , testCase .FileName )
246+ } else {
247+ fmt .Printf (" ❌ %s (%s)\n " , testCase .TestCaseName , testCase .FileName )
248+ }
249+ }
195250 }
196251
197- // Display errors if any
198252 if len (result .Errors ) > 0 {
199- fmt .Printf ("❌ Errors :\n " )
253+ fmt .Printf ("\n ❌ Output :\n " )
200254 for _ , err := range result .Errors {
201255 fmt .Printf (" - %s\n " , err )
202256 }
203257 }
204258
205- // Display warnings if any
206259 if len (result .Warnings ) > 0 {
207- fmt .Printf ("⚠️ Warnings :\n " )
260+ fmt .Printf ("\n ⚠️ Output :\n " )
208261 for _ , warning := range result .Warnings {
209262 fmt .Printf (" - %s\n " , warning )
210263 }
211264 }
265+ }
266+
267+ // matchTestResultsToTestCases matches TestResults to TestCases using FileName and TestCaseName
268+ // Returns a map of test case index to its TestResult (if found)
269+ // Uses FileName:TestCaseName as the unique identifier to ensure correct matching
270+ // Handles both patterns:
271+ // - "policy.testcase1.success.yaml" -> TestCaseName: "testcase1.success", FileName: "policy.testcase1.success.yaml"
272+ // - "policy.success.yaml" -> TestCaseName: "success", FileName: "policy.success.yaml"
273+ func matchTestResultsToTestCases (result * kyverno.ValidationResult , testCases []kyverno.TestResource ) map [int ]* kyverno.TestResult {
274+ resultMap := make (map [int ]* kyverno.TestResult )
275+
276+ // Create a map of test cases by FileName and TestCaseName for quick lookup
277+ testCaseMap := make (map [string ]int )
278+ for i , testCase := range testCases {
279+ // Use FileName and TestCaseName as the unique identifier
280+ // This handles both patterns:
281+ // - "policy.testcase1.success.yaml" with TestCaseName "testcase1.success"
282+ // - "policy.success.yaml" with TestCaseName "success"
283+ key := fmt .Sprintf ("%s:%s" , testCase .FileName , testCase .TestCaseName )
284+ testCaseMap [key ] = i
285+ }
212286
213- // Display test case results
287+ // Match TestResults to test cases using FileName and TestCaseName
214288 for _ , testResult := range result .TestResults {
215- if testResult .Passed {
216- fmt .Printf ("✓ Test case %s (%s): PASSED - Expected %s, got %s\n " ,
217- testResult .TestCaseName , testResult .FileName ,
218- testResult .ExpectedOutcome , testResult .ActualOutcome )
219- } else {
220- fmt .Printf ("❌ Test case %s (%s): FAILED - Expected %s, got %s\n " ,
221- testResult .TestCaseName , testResult .FileName ,
222- testResult .ExpectedOutcome , testResult .ActualOutcome )
289+ if testResult .FileName != "" && testResult .TestCaseName != "" {
290+ key := fmt .Sprintf ("%s:%s" , testResult .FileName , testResult .TestCaseName )
291+ if idx , exists := testCaseMap [key ]; exists {
292+ resultMap [idx ] = & testResult
293+ }
223294 }
224295 }
296+
297+ return resultMap
225298}
226299
227300// determineActualValidationResult determines if validation actually passed based on test case results
301+ // A test case passes if:
302+ // - .success.yaml file: Expected "success" and actual "success" (policy allows good resource) → PASS
303+ // - .failure.yaml file: Expected "failure" and actual "failure" (policy rejects bad resource) → PASS
304+ //
305+ // A test case fails if:
306+ // - .success.yaml file: Expected "success" but actual "failure" (policy incorrectly rejects good resource) → FAIL
307+ // - .failure.yaml file: Expected "failure" but actual "success" (policy incorrectly allows bad resource) → FAIL
228308func determineActualValidationResult (result * kyverno.ValidationResult , testCases []kyverno.TestResource ) bool {
229- // If there are test results from backend, use them
309+ // If we have TestResults, use them to match test cases by FileName and TestCaseName
230310 if len (result .TestResults ) > 0 {
231- // Create a map of test case names to expected outcomes for quick lookup
232- expectedOutcomes := make (map [string ]string )
233- for _ , testCase := range testCases {
234- expectedOutcomes [testCase .TestCaseName ] = testCase .ExpectedOutcome
235- }
236-
237- // Check each test result to see if it behaved as expected
238- for _ , testResult := range result .TestResults {
239- expectedOutcome , exists := expectedOutcomes [testResult .TestCaseName ]
240- if ! exists {
241- // If we can't find the expected outcome, fall back to the test result's expected outcome
242- expectedOutcome = testResult .ExpectedOutcome
243- }
244-
245- // A test case passes if:
246- // 1. Expected "success" and actual "success" (policy allows good resource)
247- // 2. Expected "failure" and actual "failure" (policy rejects bad resource)
248- expectedSuccess := expectedOutcome == "success"
249- actualSuccess := testResult .ActualOutcome == "success"
311+ resultMap := matchTestResultsToTestCases (result , testCases )
312+
313+ // Check each test case individually
314+ allPassed := true
315+ for i , testCase := range testCases {
316+ testResult , hasResult := resultMap [i ]
317+
318+ if hasResult {
319+ // We have a TestResult for this test case - use it directly
320+ // A test case passes if expected outcome matches actual outcome
321+ expectedSuccess := testCase .ExpectedOutcome == "success"
322+ actualSuccess := testResult .ActualOutcome == "success"
323+
324+ // Test case fails if expected outcome doesn't match actual outcome
325+ if expectedSuccess != actualSuccess {
326+ allPassed = false
327+ }
250328
251- // Test case passes if expected outcome matches actual outcome
252- if expectedSuccess != actualSuccess {
253- return false
329+ // Also check the Passed field if available
330+ if ! testResult .Passed {
331+ allPassed = false
332+ }
333+ } else {
334+ // No TestResult for this test case - we can't determine if it passed
335+ // This should not happen if the backend is working correctly
336+ // Fail validation if we can't find a result for a test case
337+ allPassed = false
254338 }
255339 }
256340
257- return true
341+ return allPassed
258342 }
259343
260- // Count test case types
261344 successTestCases := 0
262345 failureTestCases := 0
263346 for _ , testCase := range testCases {
@@ -271,11 +354,24 @@ func determineActualValidationResult(result *kyverno.ValidationResult, testCases
271354 }
272355 }
273356
274- // Simple logic:
275- // - If we have SUCCESS test cases and NO errors → PASS (policy allows good resources)
276- // - If we have FAILURE test cases and HAVE errors → PASS (policy rejects bad resources)
277- // - If we have SUCCESS test cases and HAVE errors → FAIL (policy incorrectly rejects good resources)
278- // - If we have FAILURE test cases and NO errors → FAIL (policy incorrectly allows bad resources)
357+ // Simple fallback logic:
358+ // - If we have ONLY SUCCESS test cases and NO errors → PASS
359+ // - If we have ONLY FAILURE test cases and HAVE errors → PASS
360+ // - If we have ONLY SUCCESS test cases and HAVE errors → FAIL
361+ // - If we have ONLY FAILURE test cases and NO errors → FAIL
362+ // - If we have MIXED test cases → Cannot reliably determine without TestResults
363+
364+ if successTestCases > 0 && failureTestCases > 0 {
365+ // Mixed test cases: we can't reliably determine which errors belong to which test case
366+ // Without TestResults, we cannot properly validate mixed cases
367+ // If we have errors, assume failure test cases failed (expected) and success test cases passed (expected)
368+ if len (result .Errors ) > 0 {
369+ return true
370+ } else {
371+ // No errors means failure test cases passed when they should have failed
372+ return false
373+ }
374+ }
279375
280376 if successTestCases > 0 && len (result .Errors ) == 0 {
281377 return true
0 commit comments