1- // Copyright (C) 2025 The Falco Authors
1+ // Copyright (C) 2026 The Falco Authors
22//
33// Licensed under the Apache License, Version 2.0 (the "License");
44// you may not use this file except in compliance with the License.
1717package falco
1818
1919import (
20+ "fmt"
2021 "testing"
2122
2223 "github.com/stretchr/testify/assert"
2324 appsv1 "k8s.io/api/apps/v1"
2425 corev1 "k8s.io/api/core/v1"
2526 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2627 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
28+ "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
29+ "sigs.k8s.io/structured-merge-diff/v4/typed"
2730)
2831
29- func TestNeedsUpdate (t * testing.T ) {
30- t .Run ("nil current returns needs update " , func (t * testing.T ) {
32+ func TestDiff (t * testing.T ) {
33+ t .Run ("nil current returns error " , func (t * testing.T ) {
3134 desired := & unstructured.Unstructured {
3235 Object : map [string ]interface {}{
3336 "apiVersion" : "v1" ,
3437 "kind" : "ConfigMap" ,
3538 },
3639 }
37- result , err := needsUpdate (nil , desired )
38- assert .NoError (t , err )
39- assert .True (t , result )
40+ result , err := diff (nil , desired )
41+ assert .Error (t , err )
42+ assert .Nil (t , result )
4043 })
4144
42- t .Run ("nil desired returns needs update " , func (t * testing.T ) {
45+ t .Run ("nil desired returns error " , func (t * testing.T ) {
4346 current := & corev1.ConfigMap {
4447 TypeMeta : metav1.TypeMeta {
4548 APIVersion : "v1" ,
4649 Kind : "ConfigMap" ,
4750 },
4851 ObjectMeta : metav1.ObjectMeta {Name : "test" },
4952 }
50- result , err := needsUpdate (current , nil )
51- assert .NoError (t , err )
52- assert .True (t , result )
53+ result , err := diff (current , nil )
54+ assert .Error (t , err )
55+ assert .Nil (t , result )
5356 })
5457
55- t .Run ("no managed fields returns needs update " , func (t * testing.T ) {
58+ t .Run ("no managed fields returns ErrNoManagedFields " , func (t * testing.T ) {
5659 current := & appsv1.DaemonSet {
5760 TypeMeta : metav1.TypeMeta {
5861 APIVersion : "apps/v1" ,
@@ -72,35 +75,100 @@ func TestNeedsUpdate(t *testing.T) {
7275 },
7376 },
7477 }
75- result , err := needsUpdate (current , desired )
76- assert .NoError (t , err )
77- assert .True (t , result ) // No managed fields means we need to apply
78+ result , err := diff (current , desired )
79+ assert .Error (t , err )
80+ assert .ErrorIs (t , err , ErrNoManagedFields )
81+ assert .Nil (t , result )
7882 })
7983}
8084
81- func TestDiff (t * testing.T ) {
82- t .Run ("nil current returns error" , func (t * testing.T ) {
83- desired := & unstructured.Unstructured {
84- Object : map [string ]interface {}{
85- "apiVersion" : "v1" ,
86- "kind" : "ConfigMap" ,
87- },
85+ func TestErrNoManagedFields (t * testing.T ) {
86+ t .Run ("error message is descriptive" , func (t * testing.T ) {
87+ assert .Contains (t , ErrNoManagedFields .Error (), "no managed fields" )
88+ })
89+
90+ t .Run ("can be wrapped and unwrapped" , func (t * testing.T ) {
91+ wrapped := fmt .Errorf ("failed to compare: %w" , ErrNoManagedFields )
92+ assert .ErrorIs (t , wrapped , ErrNoManagedFields )
93+ })
94+
95+ t .Run ("is distinguishable from other errors" , func (t * testing.T ) {
96+ otherErr := fmt .Errorf ("some other error" )
97+ assert .NotErrorIs (t , otherErr , ErrNoManagedFields )
98+ })
99+ }
100+
101+ func TestFormatChangedFields (t * testing.T ) {
102+ t .Run ("nil comparison returns empty string" , func (t * testing.T ) {
103+ result := formatChangedFields (nil )
104+ assert .Equal (t , "" , result )
105+ })
106+
107+ t .Run ("empty comparison returns no changes" , func (t * testing.T ) {
108+ comparison := & typed.Comparison {
109+ Added : & fieldpath.Set {},
110+ Modified : & fieldpath.Set {},
111+ Removed : & fieldpath.Set {},
88112 }
89- result , err := diff (nil , desired )
90- assert .Error (t , err )
91- assert .Nil (t , result )
113+ result := formatChangedFields (comparison )
114+ assert .Equal (t , "no changes" , result )
92115 })
93116
94- t .Run ("nil desired returns error" , func (t * testing.T ) {
95- current := & corev1.ConfigMap {
96- TypeMeta : metav1.TypeMeta {
97- APIVersion : "v1" ,
98- Kind : "ConfigMap" ,
99- },
100- ObjectMeta : metav1.ObjectMeta {Name : "test" },
117+ t .Run ("only added fields" , func (t * testing.T ) {
118+ added := fieldpath .NewSet (fieldpath .MakePathOrDie ("spec" , "replicas" ))
119+ comparison := & typed.Comparison {
120+ Added : added ,
121+ Modified : & fieldpath.Set {},
122+ Removed : & fieldpath.Set {},
101123 }
102- result , err := diff (current , nil )
103- assert .Error (t , err )
104- assert .Nil (t , result )
124+ result := formatChangedFields (comparison )
125+ assert .Contains (t , result , "added:" )
126+ assert .Contains (t , result , "spec" )
127+ assert .NotContains (t , result , "modified:" )
128+ assert .NotContains (t , result , "removed:" )
129+ })
130+
131+ t .Run ("only modified fields" , func (t * testing.T ) {
132+ modified := fieldpath .NewSet (fieldpath .MakePathOrDie ("spec" , "template" , "spec" , "containers" ))
133+ comparison := & typed.Comparison {
134+ Added : & fieldpath.Set {},
135+ Modified : modified ,
136+ Removed : & fieldpath.Set {},
137+ }
138+ result := formatChangedFields (comparison )
139+ assert .Contains (t , result , "modified:" )
140+ assert .Contains (t , result , "spec" )
141+ assert .NotContains (t , result , "added:" )
142+ assert .NotContains (t , result , "removed:" )
143+ })
144+
145+ t .Run ("only removed fields" , func (t * testing.T ) {
146+ removed := fieldpath .NewSet (fieldpath .MakePathOrDie ("metadata" , "labels" ))
147+ comparison := & typed.Comparison {
148+ Added : & fieldpath.Set {},
149+ Modified : & fieldpath.Set {},
150+ Removed : removed ,
151+ }
152+ result := formatChangedFields (comparison )
153+ assert .Contains (t , result , "removed:" )
154+ assert .Contains (t , result , "metadata" )
155+ assert .NotContains (t , result , "added:" )
156+ assert .NotContains (t , result , "modified:" )
157+ })
158+
159+ t .Run ("multiple change types" , func (t * testing.T ) {
160+ added := fieldpath .NewSet (fieldpath .MakePathOrDie ("spec" , "newField" ))
161+ modified := fieldpath .NewSet (fieldpath .MakePathOrDie ("spec" , "replicas" ))
162+ removed := fieldpath .NewSet (fieldpath .MakePathOrDie ("metadata" , "annotations" ))
163+ comparison := & typed.Comparison {
164+ Added : added ,
165+ Modified : modified ,
166+ Removed : removed ,
167+ }
168+ result := formatChangedFields (comparison )
169+ assert .Contains (t , result , "added:" )
170+ assert .Contains (t , result , "modified:" )
171+ assert .Contains (t , result , "removed:" )
172+ assert .Contains (t , result , "; " )
105173 })
106174}
0 commit comments