@@ -25,7 +25,14 @@ SOFTWARE.
2525package v3
2626
2727import (
28- "errors"
28+ "context"
29+ "fmt"
30+ v1 "github.com/pdok/smooth-operator/api/v1"
31+ "github.com/pdok/smooth-operator/model"
32+ apierrors "k8s.io/apimachinery/pkg/api/errors"
33+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34+ "k8s.io/apimachinery/pkg/runtime/schema"
35+ "k8s.io/apimachinery/pkg/util/validation/field"
2936 "os"
3037
3138 . "github.com/onsi/ginkgo/v2" //nolint:revive // ginkgo bdd
@@ -37,9 +44,11 @@ import (
3744
3845var _ = Describe ("Atom Webhook" , func () {
3946 var (
40- obj * pdoknlv3.Atom
41- oldObj * pdoknlv3.Atom
42- validator AtomCustomValidator
47+ obj * pdoknlv3.Atom
48+ oldObj * pdoknlv3.Atom
49+ validator AtomCustomValidator
50+ labelsPath * field.Path
51+ servicePath * field.Path
4352 )
4453
4554 BeforeEach (func () {
@@ -53,6 +62,8 @@ var _ = Describe("Atom Webhook", func() {
5362 Expect (obj ).NotTo (BeNil (), "Expected obj to be initialized" )
5463 // TODO (user): Add any setup logic common to all tests
5564
65+ labelsPath = field .NewPath ("metadata" ).Child ("labels" )
66+ servicePath = field .NewPath ("spec" ).Child ("service" )
5667 })
5768
5869 AfterEach (func () {
@@ -65,7 +76,11 @@ var _ = Describe("Atom Webhook", func() {
6576 })
6677
6778 It ("Should deny creation if no labels are available" , func () {
68- testCreate (validator , "invalid/no-labels.yaml" , errors .New ("Atom.pdok.nl \" asis-readonly-prod\" is invalid: metadata.labels: Required value: can't be empty" ))
79+ testCreate (validator , "invalid/no-labels.yaml" , func (_ * pdoknlv3.Atom ) field.ErrorList {
80+ return field.ErrorList {
81+ field .Required (labelsPath , "can't be empty" ),
82+ }
83+ })
6984 })
7085
7186 It ("Should create atom with ingressRouteUrls that contains the service baseUrl" , func () {
@@ -76,96 +91,205 @@ var _ = Describe("Atom Webhook", func() {
7691 testCreate (
7792 validator ,
7893 "invalid/ingress-route-urls-missing-baseurl.yaml" ,
79- errors .New ("Atom.pdok.nl \" ingress-route-urls\" is invalid: spec.ingressRouteUrls: Invalid value: \" [{http://test.com/path}]\" : must contain baseURL: http://localhost:32788/rvo/wetlands/atom" ),
94+ func (atom * pdoknlv3.Atom ) field.ErrorList {
95+ return field.ErrorList {
96+ field .Invalid (field .NewPath ("spec" ).Child ("ingressRouteUrls" ), fmt .Sprint (atom .Spec .IngressRouteURLs ), "must contain baseURL: " + atom .Spec .Service .BaseURL .String ()),
97+ }
98+ },
99+ )
100+ })
101+
102+ It ("Should deny creation if spec.service.ownerReference is not found" , func () {
103+ testCreate (
104+ validator ,
105+ "invalid/unknown-ownerref.yaml" ,
106+ func (atom * pdoknlv3.Atom ) field.ErrorList {
107+ return field.ErrorList {
108+ field .NotFound (servicePath .Child ("ownerInfoRef" ), atom .Spec .Service .OwnerInfoRef ),
109+ }
110+ },
111+ )
112+ })
113+
114+ It ("Should deny creation if spec.service.ownerReference does not contain Atom info" , func () {
115+ // Create the OwnerInfo
116+ o := v1.OwnerInfo {
117+ ObjectMeta : metav1.ObjectMeta {
118+ Name : "random" ,
119+ Namespace : "services" ,
120+ },
121+ }
122+
123+ err := validator .Client .Create (context .TODO (), & o )
124+ Expect (err ).To (Not (HaveOccurred ()))
125+
126+ testCreate (
127+ validator ,
128+ "invalid/unknown-ownerref.yaml" ,
129+ func (atom * pdoknlv3.Atom ) field.ErrorList {
130+ return field.ErrorList {
131+ field .Required (servicePath .Child ("ownerInfoRef" ), "spec.Atom missing in random" ),
132+ }
133+ },
80134 )
81135 })
82136
83137 It ("Should create and update atom without errors or warnings" , func () {
84- testUpdate (validator , "valid/minimal.yaml" , "valid/minimal-service-title-changed.yaml" , nil )
138+ testUpdate (
139+ validator ,
140+ "valid/minimal.yaml" ,
141+ func (atom * pdoknlv3.Atom ) {
142+ atom .Spec .Service .Title = "New service title"
143+ },
144+ nil ,
145+ )
85146 })
86147
87148 It ("Should deny update atom with error label names cannot be added or deleted" , func () {
88149 testUpdate (
89150 validator ,
90151 "valid/minimal.yaml" ,
91- "invalid/minimal-immutable-labels-key-change.yaml" ,
92- errors .New ("Atom.pdok.nl \" asis-readonly-prod\" is invalid: [metadata.labels.pdok.nl/dataset-id: Required value: labels cannot be removed, metadata.labels.pdok.nl/dataset-idsssssssss: Forbidden: new labels cannot be added]" ),
152+ func (atom * pdoknlv3.Atom ) {
153+ labels := atom .GetLabels ()
154+ labels ["pdok.nl/dataset-idsssssssss" ] = labels ["pdok.nl/dataset-ids" ]
155+ delete (labels , "pdok.nl/dataset-ids" )
156+ atom .Labels = labels
157+ },
158+ func (_ , _ * pdoknlv3.Atom ) field.ErrorList {
159+ return field.ErrorList {
160+ field .Forbidden (labelsPath .Child ("pdok.nl/dataset-idsssssssss" ), "new labels cannot be added" ),
161+ }
162+ },
93163 )
94164 })
95165
96166 It ("Should deny update atom with error label names are immutable" , func () {
97167 testUpdate (
98168 validator ,
99169 "valid/minimal.yaml" ,
100- "invalid/minimal-immutable-labels-value-change.yaml" ,
101- errors .New ("Atom.pdok.nl \" asis-readonly-prod\" is invalid: metadata.labels.pdok.nl/dataset-id: Invalid value: \" wetlands-changed\" : immutable: should be: wetlands" ),
170+ func (atom * pdoknlv3.Atom ) {
171+ labels := atom .GetLabels ()
172+ labels ["pdok.nl/dataset-id" ] = "wetlands-changed"
173+ atom .Labels = labels
174+ },
175+ func (old , _ * pdoknlv3.Atom ) field.ErrorList {
176+ return field.ErrorList {
177+ field .Invalid (labelsPath .Child ("pdok.nl/dataset-id" ), "wetlands-changed" , "immutable: should be: " + old .Labels ["pdok.nl/dataset-id" ]),
178+ }
179+ },
102180 )
103181 })
104182
105183 It ("Should deny update atom with error URL are immutable" , func () {
106184 testUpdate (
107185 validator ,
108186 "valid/minimal.yaml" ,
109- "invalid/minimal-immutable-url.yaml" , errors .New ("Atom.pdok.nl \" asis-readonly-prod\" is invalid: spec.service.baseUrl: Forbidden: is immutable" ),
187+ func (atom * pdoknlv3.Atom ) {
188+ // net/url.URL doesn't deepcopy...
189+ oldURL := atom .Spec .Service .BaseURL .String ()
190+ newURL , _ := model .ParseURL (oldURL )
191+ newURL .Path += "/extra"
192+ atom .Spec .Service .BaseURL = model.URL {URL : newURL }
193+ },
194+ func (_ , _ * pdoknlv3.Atom ) field.ErrorList {
195+ return field.ErrorList {
196+ field .Forbidden (servicePath .Child ("baseUrl" ), "is immutable" ),
197+ }
198+ },
110199 )
111200 })
112201
113202 It ("Should deny update atom as ingressRouteURLs cannot be removed" , func () {
114203 testUpdate (
115204 validator ,
116205 "valid/ingress-route-urls.yaml" ,
117- "invalid/ingress-route-urls-removed-url.yaml" ,
118- errors .New ("Atom.pdok.nl \" ingress-route-urls\" is invalid: spec.ingressRouteUrls: Invalid value: \" [{http://localhost:32788/rvo/wetlands/atom}]\" : urls cannot be removed, missing: {http://localhost:32788/other/path}" ),
206+ func (atom * pdoknlv3.Atom ) {
207+ atom .Spec .IngressRouteURLs = atom .Spec .IngressRouteURLs [:len (atom .Spec .IngressRouteURLs )- 1 ]
208+ },
209+ func (_ , new * pdoknlv3.Atom ) field.ErrorList {
210+ return field.ErrorList {
211+ field .Invalid (field .NewPath ("spec" ).Child ("ingressRouteUrls" ), fmt .Sprint (new .Spec .IngressRouteURLs ), "urls cannot be removed, missing: {http://localhost:32788/other/path}" ),
212+ }
213+ },
119214 )
120215 })
121216
122217 It ("Should deny update atom when the service baseUrl is changed and the old value is not added to the ingressRouteUrls" , func () {
123218 testUpdate (
124219 validator ,
125220 "valid/minimal.yaml" ,
126- "invalid/minimal-service-url-changed-ingress-route-urls-missing-old.yaml" ,
127- errors .New ("Atom.pdok.nl \" asis-readonly-prod\" is invalid: spec.ingressRouteUrls: Invalid value: \" [{http://localhost:32788/new/path}]\" : must contain baseURL: http://localhost:32788/rvo/wetlands/atom" ),
221+ func (atom * pdoknlv3.Atom ) {
222+ newURL , _ := model .ParseURL ("http://localhost:32788/new/path" )
223+
224+ atom .Spec .IngressRouteURLs = model.IngressRouteURLs {{URL : model.URL {URL : newURL }}}
225+ atom .Spec .Service .BaseURL = model.URL {URL : newURL }
226+ },
227+ func (_ , new * pdoknlv3.Atom ) field.ErrorList {
228+ return field.ErrorList {
229+ field .Invalid (field .NewPath ("spec" ).Child ("ingressRouteUrls" ), fmt .Sprint (new .Spec .IngressRouteURLs ), "must contain baseURL: http://localhost:32788/rvo/wetlands/atom" ),
230+ }
231+ },
128232 )
129233 })
130234
131235 It ("Should deny update atom when the service baseUrl is changed and the new value is not added to the ingressRouteUrls" , func () {
132236 testUpdate (
133237 validator ,
134238 "valid/minimal.yaml" ,
135- "invalid/minimal-service-url-changed-ingress-route-urls-missing-new.yaml" ,
136- errors .New ("Atom.pdok.nl \" asis-readonly-prod\" is invalid: spec.ingressRouteUrls: Invalid value: \" [{http://localhost:32788/rvo/wetlands/atom}]\" : must contain baseURL: http://localhost:32788/new/path" ),
239+ func (atom * pdoknlv3.Atom ) {
240+ oldURL := atom .Spec .Service .BaseURL
241+ newURL , _ := model .ParseURL ("http://localhost:32788/new/path" )
242+
243+ atom .Spec .IngressRouteURLs = model.IngressRouteURLs {{URL : oldURL }}
244+ atom .Spec .Service .BaseURL = model.URL {URL : newURL }
245+ },
246+ func (_ , new * pdoknlv3.Atom ) field.ErrorList {
247+ return field.ErrorList {
248+ field .Invalid (field .NewPath ("spec" ).Child ("ingressRouteUrls" ), fmt .Sprint (new .Spec .IngressRouteURLs ), "must contain baseURL: http://localhost:32788/new/path" ),
249+ }
250+ },
137251 )
138252 })
139253
140254 It ("Should create and update atom with changed service url if ingressRouteUrls is filled correctly" , func () {
141- testUpdate (validator , "valid/minimal.yaml" , "valid/minimal-service-url-changed.yaml" , nil )
255+ testUpdate (
256+ validator ,
257+ "valid/minimal.yaml" ,
258+ func (atom * pdoknlv3.Atom ) {
259+ oldURL := atom .Spec .Service .BaseURL
260+ newURL , _ := model .ParseURL ("http://localhost:32788/new/path" )
261+
262+ atom .Spec .IngressRouteURLs = model.IngressRouteURLs {{URL : oldURL }, {URL : model.URL {URL : newURL }}}
263+ atom .Spec .Service .BaseURL = model.URL {URL : newURL }
264+ },
265+ nil ,
266+ )
142267 })
143268 })
144269})
145270
146- func testUpdate (validator AtomCustomValidator , createFile , updateFile string , expectedError error ) {
271+ func testUpdate (validator AtomCustomValidator , createFile string , updateFn func ( atom * pdoknlv3. Atom ), errFn func ( atomOld , atomNew * pdoknlv3. Atom ) field. ErrorList ) {
147272 atomOld := testCreate (validator , createFile , nil )
148273
149274 By ("Simulating an (in)valid update scenario" )
150- input , err := os .ReadFile ("test_data/updates/" + updateFile )
151- Expect (err ).NotTo (HaveOccurred ())
152- atomNew := & pdoknlv3.Atom {}
153- err = yaml .Unmarshal (input , atomNew )
154- Expect (err ).NotTo (HaveOccurred ())
155- Expect (atomOld .GetName ()).To (Equal (atomNew .GetName ()))
156- warnings , errorsUpdate := validator .ValidateUpdate (ctx , atomOld , atomNew )
275+ atomNew := atomOld .DeepCopy ()
276+ updateFn (atomNew )
277+
278+ warnings , err := validator .ValidateUpdate (ctx , atomOld , atomNew )
157279
158280 Expect (len (warnings )).To (Equal (0 ))
159281
160- if expectedError == nil {
161- Expect (errorsUpdate ).To (Not (HaveOccurred ()))
282+ if errFn == nil {
283+ Expect (err ).To (Not (HaveOccurred ()))
162284 } else {
163- Expect (errorsUpdate ).To (HaveOccurred ())
164- Expect (expectedError .Error ()).To (Equal (errorsUpdate .Error ()))
285+ Expect (err ).To (HaveOccurred ())
286+ Expect (
287+ apierrors .NewInvalid (schema.GroupKind {Group : "pdok.nl" , Kind : "Atom" }, atomNew .Name , errFn (atomOld , atomNew )).Error (),
288+ ).To (Equal (err .Error ()))
165289 }
166290}
167291
168- func testCreate (validator AtomCustomValidator , createFile string , expectedError error ) * pdoknlv3.Atom {
292+ func testCreate (validator AtomCustomValidator , createFile string , errFn func ( atom * pdoknlv3. Atom ) field. ErrorList ) * pdoknlv3.Atom {
169293 By ("simulating a (in)valid creation scenario" )
170294 input , err := os .ReadFile ("test_data/creates/" + createFile )
171295 Expect (err ).NotTo (HaveOccurred ())
@@ -175,10 +299,13 @@ func testCreate(validator AtomCustomValidator, createFile string, expectedError
175299 warnings , err := validator .ValidateCreate (ctx , atom )
176300 Expect (len (warnings )).To (Equal (0 ))
177301
178- if expectedError == nil {
302+ if errFn == nil {
179303 Expect (err ).To (Not (HaveOccurred ()))
180304 } else {
181- Expect (expectedError .Error ()).To (Equal (err .Error ()))
305+ Expect (err ).To (HaveOccurred ())
306+ Expect (
307+ apierrors .NewInvalid (schema.GroupKind {Group : "pdok.nl" , Kind : "Atom" }, atom .Name , errFn (atom )).Error (),
308+ ).To (Equal (err .Error ()))
182309 }
183310
184311 return atom
0 commit comments