pepr 0.38.0-rc → 0.38.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/cli.js +13 -13
  2. package/dist/controller.js +1 -1
  3. package/dist/fixtures/loader.d.ts +1 -1
  4. package/dist/fixtures/loader.d.ts.map +1 -1
  5. package/dist/lib/controller/index.d.ts +2 -1
  6. package/dist/lib/controller/index.d.ts.map +1 -1
  7. package/dist/lib/k8s.d.ts +1 -77
  8. package/dist/lib/k8s.d.ts.map +1 -1
  9. package/dist/lib.js.map +2 -2
  10. package/package.json +8 -6
  11. package/src/fixtures/loader.ts +1 -1
  12. package/src/lib/assets/pods.ts +6 -6
  13. package/src/lib/assets/yaml.ts +6 -6
  14. package/src/lib/controller/index.ts +2 -2
  15. package/src/lib/k8s.ts +1 -102
  16. package/dist/cli/init/utils.test.d.ts +0 -2
  17. package/dist/cli/init/utils.test.d.ts.map +0 -1
  18. package/dist/cli/init/walkthrough.test.d.ts +0 -2
  19. package/dist/cli/init/walkthrough.test.d.ts.map +0 -1
  20. package/dist/lib/adjudicators.test.d.ts +0 -2
  21. package/dist/lib/adjudicators.test.d.ts.map +0 -1
  22. package/dist/lib/assets/helm.test.d.ts +0 -2
  23. package/dist/lib/assets/helm.test.d.ts.map +0 -1
  24. package/dist/lib/assets/pods.test.d.ts +0 -2
  25. package/dist/lib/assets/pods.test.d.ts.map +0 -1
  26. package/dist/lib/capability.test.d.ts +0 -2
  27. package/dist/lib/capability.test.d.ts.map +0 -1
  28. package/dist/lib/controller/store.test.d.ts +0 -2
  29. package/dist/lib/controller/store.test.d.ts.map +0 -1
  30. package/dist/lib/errors.test.d.ts +0 -2
  31. package/dist/lib/errors.test.d.ts.map +0 -1
  32. package/dist/lib/filter.test.d.ts +0 -3
  33. package/dist/lib/filter.test.d.ts.map +0 -1
  34. package/dist/lib/finalizer.test.d.ts +0 -2
  35. package/dist/lib/finalizer.test.d.ts.map +0 -1
  36. package/dist/lib/helpers.test.d.ts +0 -2
  37. package/dist/lib/helpers.test.d.ts.map +0 -1
  38. package/dist/lib/included-files.test.d.ts +0 -2
  39. package/dist/lib/included-files.test.d.ts.map +0 -1
  40. package/dist/lib/logger.test.d.ts +0 -2
  41. package/dist/lib/logger.test.d.ts.map +0 -1
  42. package/dist/lib/metrics.test.d.ts +0 -2
  43. package/dist/lib/metrics.test.d.ts.map +0 -1
  44. package/dist/lib/module.test.d.ts +0 -2
  45. package/dist/lib/module.test.d.ts.map +0 -1
  46. package/dist/lib/mutate-request.test.d.ts +0 -2
  47. package/dist/lib/mutate-request.test.d.ts.map +0 -1
  48. package/dist/lib/queue.test.d.ts +0 -2
  49. package/dist/lib/queue.test.d.ts.map +0 -1
  50. package/dist/lib/schedule.test.d.ts +0 -15
  51. package/dist/lib/schedule.test.d.ts.map +0 -1
  52. package/dist/lib/storage.test.d.ts +0 -2
  53. package/dist/lib/storage.test.d.ts.map +0 -1
  54. package/dist/lib/tls.test.d.ts +0 -2
  55. package/dist/lib/tls.test.d.ts.map +0 -1
  56. package/dist/lib/utils.test.d.ts +0 -2
  57. package/dist/lib/utils.test.d.ts.map +0 -1
  58. package/dist/lib/validate-request.test.d.ts +0 -2
  59. package/dist/lib/validate-request.test.d.ts.map +0 -1
  60. package/dist/lib/watch-processor.test.d.ts +0 -2
  61. package/dist/lib/watch-processor.test.d.ts.map +0 -1
  62. package/dist/sdk/sdk.test.d.ts +0 -2
  63. package/dist/sdk/sdk.test.d.ts.map +0 -1
  64. package/src/cli/init/utils.test.ts +0 -19
  65. package/src/cli/init/walkthrough.test.ts +0 -102
  66. package/src/lib/adjudicators.test.ts +0 -1236
  67. package/src/lib/assets/helm.test.ts +0 -64
  68. package/src/lib/assets/pods.test.ts +0 -553
  69. package/src/lib/capability.test.ts +0 -655
  70. package/src/lib/controller/store.test.ts +0 -131
  71. package/src/lib/errors.test.ts +0 -85
  72. package/src/lib/filter.test.ts +0 -691
  73. package/src/lib/finalizer.test.ts +0 -236
  74. package/src/lib/helpers.test.ts +0 -1486
  75. package/src/lib/included-files.test.ts +0 -22
  76. package/src/lib/logger.test.ts +0 -18
  77. package/src/lib/metrics.test.ts +0 -132
  78. package/src/lib/module.test.ts +0 -126
  79. package/src/lib/mutate-request.test.ts +0 -187
  80. package/src/lib/queue.test.ts +0 -152
  81. package/src/lib/schedule.test.ts +0 -217
  82. package/src/lib/storage.test.ts +0 -216
  83. package/src/lib/tls.test.ts +0 -18
  84. package/src/lib/utils.test.ts +0 -69
  85. package/src/lib/validate-request.test.ts +0 -121
  86. package/src/lib/watch-processor.test.ts +0 -418
  87. package/src/sdk/sdk.test.ts +0 -276
@@ -1,1486 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
-
4
- import { Binding, CapabilityExport, Event } from "./types";
5
- import {
6
- addVerbIfNotExists,
7
- bindingAndCapabilityNSConflict,
8
- createDirectoryIfNotExists,
9
- createRBACMap,
10
- checkDeploymentStatus,
11
- filterNoMatchReason,
12
- dedent,
13
- generateWatchNamespaceError,
14
- hasAnyOverlap,
15
- hasEveryOverlap,
16
- ignoredNamespaceConflict,
17
- matchesRegex,
18
- namespaceDeploymentsReady,
19
- namespaceComplianceValidator,
20
- parseTimeout,
21
- replaceString,
22
- secretOverLimit,
23
- validateHash,
24
- validateCapabilityNames,
25
- ValidationError,
26
- } from "./helpers";
27
- import { sanitizeResourceName } from "../sdk/sdk";
28
- import * as fc from "fast-check";
29
- import { expect, describe, test, jest, beforeEach, afterEach } from "@jest/globals";
30
- import { promises as fs } from "fs";
31
- import { SpiedFunction } from "jest-mock";
32
- import { K8s, GenericClass, KubernetesObject, kind } from "kubernetes-fluent-client";
33
- import { K8sInit } from "kubernetes-fluent-client/dist/fluent/types";
34
-
35
- export const callback = () => undefined;
36
-
37
- jest.mock("kubernetes-fluent-client", () => {
38
- return {
39
- K8s: jest.fn(),
40
- kind: jest.fn(),
41
- };
42
- });
43
-
44
- jest.mock("fs", () => {
45
- return {
46
- promises: {
47
- access: jest.fn(),
48
- mkdir: jest.fn(),
49
- },
50
- };
51
- });
52
-
53
- const mockCapabilities: CapabilityExport[] = JSON.parse(`[
54
- {
55
- "name": "hello-pepr",
56
- "description": "A simple example capability to show how things work.",
57
- "namespaces": [
58
- "pepr-demo",
59
- "pepr-demo-2"
60
- ],
61
- "bindings": [
62
- {
63
- "kind": {
64
- "kind": "Namespace",
65
- "version": "v1",
66
- "group": ""
67
- },
68
- "event": "CREATE",
69
- "filters": {
70
- "name": "",
71
- "namespaces": [],
72
- "labels": {},
73
- "annotations": {}
74
- },
75
- "isMutate": true
76
- },
77
- {
78
- "kind": {
79
- "kind": "Namespace",
80
- "version": "v1",
81
- "group": ""
82
- },
83
- "event": "DELETE",
84
- "filters": {
85
- "name": "pepr-demo-2",
86
- "namespaces": [],
87
- "labels": {},
88
- "annotations": {}
89
- },
90
- "isWatch": true
91
- },
92
- {
93
- "kind": {
94
- "kind": "ConfigMap",
95
- "version": "v1",
96
- "group": ""
97
- },
98
- "event": "CREATE",
99
- "filters": {
100
- "name": "example-1",
101
- "namespaces": [],
102
- "labels": {},
103
- "annotations": {}
104
- },
105
- "isMutate": true
106
- },
107
- {
108
- "kind": {
109
- "kind": "ConfigMap",
110
- "version": "v1",
111
- "group": ""
112
- },
113
- "event": "UPDATE",
114
- "filters": {
115
- "name": "example-2",
116
- "namespaces": [],
117
- "labels": {},
118
- "annotations": {}
119
- },
120
- "isMutate": true
121
- },
122
- {
123
- "kind": {
124
- "kind": "ConfigMap",
125
- "version": "v1",
126
- "group": ""
127
- },
128
- "event": "CREATE",
129
- "filters": {
130
- "name": "example-2",
131
- "namespaces": [],
132
- "labels": {},
133
- "annotations": {}
134
- },
135
- "isValidate": true
136
- },
137
- {
138
- "kind": {
139
- "kind": "ConfigMap",
140
- "version": "v1",
141
- "group": ""
142
- },
143
- "event": "CREATE",
144
- "filters": {
145
- "name": "example-2",
146
- "namespaces": [],
147
- "labels": {},
148
- "annotations": {}
149
- },
150
- "isWatch": true
151
- },
152
- {
153
- "kind": {
154
- "kind": "ConfigMap",
155
- "version": "v1",
156
- "group": ""
157
- },
158
- "event": "CREATE",
159
- "filters": {
160
- "name": "",
161
- "namespaces": [],
162
- "labels": {},
163
- "annotations": {}
164
- },
165
- "isValidate": true
166
- },
167
- {
168
- "kind": {
169
- "kind": "ConfigMap",
170
- "version": "v1",
171
- "group": ""
172
- },
173
- "event": "CREATEORUPDATE",
174
- "filters": {
175
- "name": "",
176
- "namespaces": [],
177
- "labels": {
178
- "change": "by-label"
179
- },
180
- "annotations": {}
181
- },
182
- "isMutate": true
183
- },
184
- {
185
- "kind": {
186
- "kind": "ConfigMap",
187
- "version": "v1",
188
- "group": ""
189
- },
190
- "event": "DELETE",
191
- "filters": {
192
- "name": "",
193
- "namespaces": [],
194
- "labels": {
195
- "change": "by-label"
196
- },
197
- "annotations": {}
198
- },
199
- "isValidate": true
200
- },
201
- {
202
- "kind": {
203
- "kind": "ConfigMap",
204
- "version": "v1",
205
- "group": ""
206
- },
207
- "event": "CREATE",
208
- "filters": {
209
- "name": "example-4",
210
- "namespaces": [],
211
- "labels": {},
212
- "annotations": {}
213
- },
214
- "isMutate": true
215
- },
216
- {
217
- "kind": {
218
- "kind": "ConfigMap",
219
- "version": "v1",
220
- "group": ""
221
- },
222
- "event": "CREATE",
223
- "filters": {
224
- "name": "example-4a",
225
- "namespaces": [
226
- "pepr-demo-2"
227
- ],
228
- "labels": {},
229
- "annotations": {}
230
- },
231
- "isMutate": true
232
- },
233
- {
234
- "kind": {
235
- "kind": "ConfigMap",
236
- "version": "v1",
237
- "group": ""
238
- },
239
- "event": "CREATE",
240
- "filters": {
241
- "name": "",
242
- "namespaces": [],
243
- "labels": {
244
- "chuck-norris": ""
245
- },
246
- "annotations": {}
247
- },
248
- "isMutate": true
249
- },
250
- {
251
- "kind": {
252
- "kind": "Secret",
253
- "version": "v1",
254
- "group": ""
255
- },
256
- "event": "CREATE",
257
- "filters": {
258
- "name": "secret-1",
259
- "namespaces": [],
260
- "labels": {},
261
- "annotations": {}
262
- },
263
- "isMutate": true
264
- },
265
- {
266
- "kind": {
267
- "group": "pepr.dev",
268
- "version": "v1",
269
- "kind": "Unicorn"
270
- },
271
- "event": "CREATE",
272
- "filters": {
273
- "name": "example-1",
274
- "namespaces": [],
275
- "labels": {},
276
- "annotations": {}
277
- },
278
- "isMutate": true
279
- },
280
- {
281
- "kind": {
282
- "group": "pepr.dev",
283
- "version": "v1",
284
- "kind": "Unicorn"
285
- },
286
- "event": "CREATE",
287
- "filters": {
288
- "name": "example-2",
289
- "namespaces": [],
290
- "labels": {},
291
- "annotations": {}
292
- },
293
- "isMutate": true
294
- }
295
- ]
296
- }
297
- ]`);
298
-
299
- describe("validateCapabilityNames Property-Based Tests", () => {
300
- test("should only accept names that are valid after sanitation", () => {
301
- fc.assert(
302
- fc.property(
303
- fc.array(
304
- fc.record({
305
- name: fc.string(),
306
- bindings: fc.array(fc.anything()),
307
- hasSchedule: fc.boolean(),
308
- }),
309
- ),
310
- capabilities => {
311
- if (capabilities.every(cap => cap.name === sanitizeResourceName(cap.name))) {
312
- expect(() => validateCapabilityNames(capabilities as CapabilityExport[])).not.toThrow();
313
- } else {
314
- expect(() => validateCapabilityNames(capabilities as CapabilityExport[])).toThrowError(
315
- /not a valid Kubernetes resource name/,
316
- );
317
- }
318
- },
319
- ),
320
- );
321
- });
322
- });
323
-
324
- describe("validateCapabilityNames", () => {
325
- test("should return true if all capability names are valid", () => {
326
- const capabilities = mockCapabilities;
327
- expect(() => validateCapabilityNames(capabilities)).not.toThrow();
328
- });
329
-
330
- test("should throw an error if a capability name is invalid", () => {
331
- const capabilities = mockCapabilities;
332
- capabilities[0].name = "invalid name";
333
- expect(() => validateCapabilityNames(capabilities)).toThrowError(ValidationError);
334
- });
335
-
336
- test("should ignore when capabilities are not loaded", () => {
337
- expect(validateCapabilityNames(undefined)).toBe(undefined);
338
- });
339
- });
340
-
341
- describe("createRBACMap", () => {
342
- test("should return the correct RBACMap for given capabilities", () => {
343
- const result = createRBACMap(mockCapabilities);
344
-
345
- const expected = {
346
- "pepr.dev/v1/peprstore": {
347
- verbs: ["create", "get", "patch", "watch"],
348
- plural: "peprstores",
349
- },
350
- "apiextensions.k8s.io/v1/customresourcedefinition": {
351
- verbs: ["patch", "create"],
352
- plural: "customresourcedefinitions",
353
- },
354
- "/v1/Namespace": { verbs: ["watch"], plural: "namespaces" },
355
- "/v1/ConfigMap": { verbs: ["watch"], plural: "configmaps" },
356
- };
357
-
358
- expect(result).toEqual(expected);
359
- });
360
- });
361
-
362
- describe("addVerbIfNotExists", () => {
363
- test("should add a verb if it does not exist in the array", () => {
364
- const verbs = ["get", "list"];
365
- addVerbIfNotExists(verbs, "watch");
366
- expect(verbs).toEqual(["get", "list", "watch"]);
367
- });
368
-
369
- test("should not add a verb if it already exists in the array", () => {
370
- const verbs = ["get", "list", "watch"];
371
- addVerbIfNotExists(verbs, "get");
372
- expect(verbs).toEqual(["get", "list", "watch"]); // The array remains unchanged
373
- });
374
- });
375
-
376
- describe("createDirectoryIfNotExists function", () => {
377
- test("should create a directory if it doesn't exist", async () => {
378
- (fs.access as jest.Mock).mockRejectedValue({ code: "ENOENT" } as never);
379
- (fs.mkdir as jest.Mock).mockResolvedValue(undefined as never);
380
-
381
- const directoryPath = "/pepr/pepr-test-module/asdf";
382
-
383
- await createDirectoryIfNotExists(directoryPath);
384
-
385
- expect(fs.access).toHaveBeenCalledWith(directoryPath);
386
- expect(fs.mkdir).toHaveBeenCalledWith(directoryPath, { recursive: true });
387
- });
388
-
389
- test("should not create a directory if it already exists", async () => {
390
- jest.resetAllMocks();
391
- (fs.access as jest.Mock).mockResolvedValue(undefined as never);
392
-
393
- const directoryPath = "/pepr/pepr-test-module/asdf";
394
-
395
- await createDirectoryIfNotExists(directoryPath);
396
-
397
- expect(fs.access).toHaveBeenCalledWith(directoryPath);
398
- expect(fs.mkdir).not.toHaveBeenCalled();
399
- });
400
-
401
- test("should throw an error for other fs.access errors", async () => {
402
- jest.resetAllMocks();
403
- (fs.access as jest.Mock).mockRejectedValue({ code: "ERROR" } as never);
404
-
405
- const directoryPath = "/pepr/pepr-test-module/asdf";
406
-
407
- try {
408
- await createDirectoryIfNotExists(directoryPath);
409
- } catch (error) {
410
- expect(error.code).toEqual("ERROR");
411
- }
412
- });
413
- });
414
-
415
- describe("hasAnyOverlap", () => {
416
- test("returns true for overlapping arrays", () => {
417
- expect(hasAnyOverlap([1, 2, 3], [3, 4, 5])).toBe(true);
418
- });
419
-
420
- test("returns false for non-overlapping arrays", () => {
421
- expect(hasAnyOverlap([1, 2, 3], [4, 5, 6])).toBe(false);
422
- });
423
-
424
- test("returns false for empty arrays", () => {
425
- expect(hasAnyOverlap([], [1, 2, 3])).toBe(false);
426
- expect(hasAnyOverlap([1, 2, 3], [])).toBe(false);
427
- });
428
-
429
- test("returns false for two empty arrays", () => {
430
- expect(hasAnyOverlap([], [])).toBe(false);
431
- });
432
- });
433
-
434
- describe("hasEveryOverlap", () => {
435
- test("returns true if all elements in array1 are in array2", () => {
436
- expect(hasEveryOverlap([1, 2], [1, 2, 3])).toBe(true);
437
- });
438
-
439
- test("returns false if any element in array1 is not in array2", () => {
440
- expect(hasEveryOverlap([1, 2, 4], [1, 2, 3])).toBe(false);
441
- });
442
-
443
- test("returns true if array1 is empty", () => {
444
- expect(hasEveryOverlap([], [1, 2, 3])).toBe(true);
445
- });
446
-
447
- test("returns false if array2 is empty", () => {
448
- expect(hasEveryOverlap([1, 2], [])).toBe(false);
449
- });
450
-
451
- test("returns true if both arrays are empty", () => {
452
- expect(hasEveryOverlap([], [])).toBe(true);
453
- });
454
- });
455
-
456
- describe("ignoredNamespaceConflict", () => {
457
- test("returns true if there is an overlap", () => {
458
- expect(ignoredNamespaceConflict(["ns1", "ns2"], ["ns2", "ns3"])).toBe(true);
459
- });
460
-
461
- test("returns false if there is no overlap", () => {
462
- expect(ignoredNamespaceConflict(["ns1", "ns2"], ["ns3", "ns4"])).toBe(false);
463
- });
464
-
465
- test("returns false if either array is empty", () => {
466
- expect(ignoredNamespaceConflict([], ["ns1", "ns2"])).toBe(false);
467
- expect(ignoredNamespaceConflict(["ns1", "ns2"], [])).toBe(false);
468
- });
469
-
470
- test("returns false if both arrays are empty", () => {
471
- expect(ignoredNamespaceConflict([], [])).toBe(false);
472
- });
473
- });
474
-
475
- describe("bindingAndCapabilityNSConflict", () => {
476
- test("returns false if capabilityNamespaces is empty", () => {
477
- expect(bindingAndCapabilityNSConflict(["ns1", "ns2"], [])).toBe(false);
478
- });
479
-
480
- test("returns true if capability namespaces are not empty and there is no overlap with binding namespaces", () => {
481
- expect(bindingAndCapabilityNSConflict(["ns1", "ns2"], ["ns3", "ns4"])).toBe(true);
482
- });
483
-
484
- test("returns true if capability namespaces are not empty and there is an overlap", () => {
485
- expect(bindingAndCapabilityNSConflict(["ns1", "ns2"], ["ns2", "ns3"])).toBe(true);
486
- });
487
-
488
- test("returns false if both arrays are empty", () => {
489
- expect(bindingAndCapabilityNSConflict([], [])).toBe(false);
490
- });
491
- });
492
-
493
- describe("generateWatchNamespaceError", () => {
494
- test("returns error for ignored namespace conflict", () => {
495
- const error = generateWatchNamespaceError(["ns1"], ["ns1"], []);
496
- expect(error).toBe("Binding uses a Pepr ignored namespace: ignoredNamespaces: [ns1] bindingNamespaces: [ns1].");
497
- });
498
-
499
- test("returns error for binding and capability namespace conflict", () => {
500
- const error = generateWatchNamespaceError([""], ["ns2"], ["ns3"]);
501
- expect(error).toBe(
502
- "Binding uses namespace not governed by capability: bindingNamespaces: [ns2] capabilityNamespaces: [ns3].",
503
- );
504
- });
505
-
506
- test("returns combined error for both conflicts", () => {
507
- const error = generateWatchNamespaceError(["ns1"], ["ns1"], ["ns3", "ns4"]);
508
- expect(error).toBe(
509
- "Binding uses a Pepr ignored namespace: ignoredNamespaces: [ns1] bindingNamespaces: [ns1]. Binding uses namespace not governed by capability: bindingNamespaces: [ns1] capabilityNamespaces: [ns3, ns4].",
510
- );
511
- });
512
-
513
- test("returns empty string when there are no conflicts", () => {
514
- const error = generateWatchNamespaceError([], ["ns2"], []);
515
- expect(error).toBe("");
516
- });
517
- });
518
-
519
- const nsViolation: CapabilityExport[] = JSON.parse(`[
520
- {
521
- "name": "test-capability-namespaces",
522
- "description": "Should be confined to namespaces listed in capabilities and not be able to use ignored namespaces",
523
- "namespaces": [
524
- "miami",
525
- "dallas",
526
- "milwaukee"
527
- ],
528
- "bindings": [
529
- {
530
- "kind": {
531
- "kind": "Namespace",
532
- "version": "v1",
533
- "group": ""
534
- },
535
- "event": "CREATE",
536
- "filters": {
537
- "name": "",
538
- "namespaces": ["new york"],
539
- "labels": {},
540
- "annotations": {}
541
- },
542
- "isMutate": true
543
- }
544
- ]
545
- }
546
- ]`);
547
-
548
- const allNSCapabilities: CapabilityExport[] = JSON.parse(`[
549
- {
550
- "name": "test-capability-namespaces",
551
- "description": "Should be confined to namespaces listed in capabilities and not be able to use ignored namespaces",
552
- "namespaces": [],
553
- "bindings": [
554
- {
555
- "kind": {
556
- "kind": "Namespace",
557
- "version": "v1",
558
- "group": ""
559
- },
560
- "event": "CREATE",
561
- "filters": {
562
- "name": "",
563
- "namespaces": ["new york"],
564
- "labels": {},
565
- "annotations": {}
566
- },
567
- "isMutate": true
568
- }
569
- ]
570
- }
571
- ]`);
572
-
573
- const nonNsViolation: CapabilityExport[] = JSON.parse(`[
574
- {
575
- "name": "test-capability-namespaces",
576
- "description": "Should be confined to namespaces listed in capabilities and not be able to use ignored namespaces",
577
- "namespaces": [
578
- "miami",
579
- "dallas",
580
- "milwaukee"
581
- ],
582
- "bindings": [
583
- {
584
- "kind": {
585
- "kind": "Namespace",
586
- "version": "v1",
587
- "group": ""
588
- },
589
- "event": "CREATE",
590
- "filters": {
591
- "name": "",
592
- "namespaces": ["miami"],
593
- "labels": {},
594
- "annotations": {}
595
- },
596
- "isMutate": true
597
- }
598
- ]
599
- }
600
- ]`);
601
-
602
- describe("namespaceComplianceValidator", () => {
603
- let errorSpy: SpiedFunction<{ (...data: unknown[]): void; (message?: unknown, ...optionalParams: unknown[]): void }>;
604
- beforeEach(() => {
605
- errorSpy = jest.spyOn(console, "error").mockImplementation(() => {});
606
- });
607
-
608
- afterEach(() => {
609
- errorSpy.mockRestore();
610
- });
611
- test("should throw error for invalid regex namespaces", () => {
612
- const nsViolationCapability: CapabilityExport = {
613
- ...nonNsViolation[0],
614
- bindings: nonNsViolation[0].bindings.map(binding => ({
615
- ...binding,
616
- filters: {
617
- ...binding.filters,
618
- namespaces: [],
619
- regexNamespaces: [new RegExp(/^system/).source],
620
- },
621
- })),
622
- };
623
- expect(() => {
624
- namespaceComplianceValidator(nsViolationCapability);
625
- }).toThrowError(
626
- `Ignoring Watch Callback: Object namespace does not match any capability namespace with regex ${nsViolationCapability.bindings[0].filters.regexNamespaces[0]}.`,
627
- );
628
- });
629
- test("should not throw an error for valid regex namespaces", () => {
630
- const nonnsViolationCapability: CapabilityExport = {
631
- ...nonNsViolation[0],
632
- bindings: nonNsViolation[0].bindings.map(binding => ({
633
- ...binding,
634
- filters: {
635
- ...binding.filters,
636
- namespaces: [],
637
- regexNamespaces: [new RegExp(/^mia/).source],
638
- },
639
- })),
640
- };
641
- expect(() => {
642
- namespaceComplianceValidator(nonnsViolationCapability);
643
- }).not.toThrow();
644
- });
645
- test("should throw error for invalid regex ignored namespaces", () => {
646
- const nsViolationCapability: CapabilityExport = {
647
- ...nonNsViolation[0],
648
- bindings: nonNsViolation[0].bindings.map(binding => ({
649
- ...binding,
650
- filters: {
651
- ...binding.filters,
652
- namespaces: [],
653
- regexNamespaces: [new RegExp(/^mia/).source],
654
- },
655
- })),
656
- };
657
- expect(() => {
658
- namespaceComplianceValidator(nsViolationCapability, ["miami"]);
659
- }).toThrowError(
660
- `Ignoring Watch Callback: Regex namespace: ${nsViolationCapability.bindings[0].filters.regexNamespaces[0]}, is an ignored namespace: miami.`,
661
- );
662
- });
663
- test("should not throw an error for valid regex ignored namespaces", () => {
664
- const nonnsViolationCapability: CapabilityExport = {
665
- ...nonNsViolation[0],
666
- bindings: nonNsViolation[0].bindings.map(binding => ({
667
- ...binding,
668
- filters: {
669
- ...binding.filters,
670
- namespaces: [],
671
- regexNamespaces: [new RegExp(/^mia/).source],
672
- },
673
- })),
674
- };
675
- expect(() => {
676
- namespaceComplianceValidator(nonnsViolationCapability, ["Seattle"]);
677
- }).not.toThrow();
678
- });
679
- test("should not throw an error for valid namespaces", () => {
680
- expect(() => {
681
- namespaceComplianceValidator(nonNsViolation[0]);
682
- }).not.toThrow();
683
- });
684
-
685
- test("should throw an error for binding namespace using a non capability namespace", () => {
686
- try {
687
- namespaceComplianceValidator(nsViolation[0]);
688
- } catch (e) {
689
- expect(e.message).toBe(
690
- "Error in test-capability-namespaces capability. A binding violates namespace rules. Please check ignoredNamespaces and capability namespaces: Binding uses namespace not governed by capability: bindingNamespaces: [new york] capabilityNamespaces: [miami, dallas, milwaukee].",
691
- );
692
- }
693
- });
694
-
695
- test("should throw an error for binding namespace using an ignored namespace: Part 1", () => {
696
- /*
697
- * this test case lists miami as a capability namespace, but also as an ignored namespace
698
- * in this case, there should be an error since ignoredNamespaces have precedence
699
- */
700
- try {
701
- namespaceComplianceValidator(nonNsViolation[0], ["miami"]);
702
- } catch (e) {
703
- expect(e.message).toBe(
704
- "Error in test-capability-namespaces capability. A binding violates namespace rules. Please check ignoredNamespaces and capability namespaces: Binding uses a Pepr ignored namespace: ignoredNamespaces: [miami] bindingNamespaces: [miami].",
705
- );
706
- }
707
- });
708
-
709
- test("should throw an error for binding namespace using an ignored namespace: Part 2", () => {
710
- /*
711
- * This capability uses all namespaces but new york should be ignored
712
- * the binding uses new york so it should fail
713
- */
714
- try {
715
- namespaceComplianceValidator(allNSCapabilities[0], ["new york"]);
716
- } catch (e) {
717
- expect(e.message).toBe(
718
- "Error in test-capability-namespaces capability. A binding violates namespace rules. Please check ignoredNamespaces and capability namespaces: Binding uses a Pepr ignored namespace: ignoredNamespaces: [new york] bindingNamespaces: [new york].",
719
- );
720
- }
721
- });
722
- });
723
-
724
- describe("checkDeploymentStatus", () => {
725
- const mockK8s = jest.mocked(K8s);
726
-
727
- beforeEach(() => {
728
- jest.useFakeTimers();
729
- });
730
- afterEach(() => {
731
- jest.clearAllMocks();
732
- jest.resetAllMocks();
733
- jest.useRealTimers();
734
- });
735
- test("should return true if all deployments are ready", async () => {
736
- const deployments = {
737
- items: [
738
- {
739
- metadata: {
740
- name: "watcher",
741
- namespace: "pepr-system",
742
- },
743
- spec: {
744
- replicas: 1,
745
- },
746
- status: {
747
- readyReplicas: 1,
748
- },
749
- },
750
- {
751
- metadata: {
752
- name: "admission",
753
- namespace: "pepr-system",
754
- },
755
- spec: {
756
- replicas: 2,
757
- },
758
- status: {
759
- readyReplicas: 2,
760
- },
761
- },
762
- ],
763
- };
764
-
765
- mockK8s.mockImplementation(<T extends GenericClass, K extends KubernetesObject>() => {
766
- return {
767
- InNamespace: jest.fn().mockReturnThis(),
768
- Get: () => deployments,
769
- } as unknown as K8sInit<T, K>;
770
- });
771
-
772
- const expected = true;
773
- const result = await checkDeploymentStatus("pepr-system");
774
- expect(result).toBe(expected);
775
- });
776
-
777
- test("should return false if any deployments are not ready", async () => {
778
- const deployments = {
779
- items: [
780
- {
781
- metadata: {
782
- name: "watcher",
783
- namespace: "pepr-system",
784
- },
785
- spec: {
786
- replicas: 1,
787
- },
788
- status: {
789
- readyReplicas: 1,
790
- },
791
- },
792
- {
793
- metadata: {
794
- name: "admission",
795
- namespace: "pepr-system",
796
- },
797
- spec: {
798
- replicas: 2,
799
- },
800
- status: {
801
- readyReplicas: 1,
802
- },
803
- },
804
- ],
805
- };
806
-
807
- mockK8s.mockImplementation(<T extends GenericClass, K extends KubernetesObject>() => {
808
- return {
809
- InNamespace: jest.fn().mockReturnThis(),
810
- Get: () => deployments,
811
- } as unknown as K8sInit<T, K>;
812
- });
813
-
814
- const expected = false;
815
- const result = await checkDeploymentStatus("pepr-system");
816
- expect(result).toBe(expected);
817
- });
818
- });
819
-
820
- describe("namespaceDeploymentsReady", () => {
821
- const mockK8s = jest.mocked(K8s);
822
-
823
- beforeEach(() => {
824
- jest.useFakeTimers();
825
- });
826
- afterEach(() => {
827
- jest.clearAllMocks();
828
- jest.resetAllMocks();
829
- jest.useRealTimers();
830
- });
831
-
832
- test("should return true if all deployments are ready", async () => {
833
- const deployments = {
834
- items: [
835
- {
836
- metadata: {
837
- name: "watcher",
838
- namespace: "pepr-system",
839
- },
840
- spec: {
841
- replicas: 1,
842
- },
843
- status: {
844
- readyReplicas: 1,
845
- },
846
- },
847
- {
848
- metadata: {
849
- name: "admission",
850
- namespace: "pepr-system",
851
- },
852
- spec: {
853
- replicas: 2,
854
- },
855
- status: {
856
- readyReplicas: 2,
857
- },
858
- },
859
- ],
860
- };
861
-
862
- mockK8s.mockImplementation(<T extends GenericClass, K extends KubernetesObject>() => {
863
- return {
864
- InNamespace: jest.fn().mockReturnThis(),
865
- Get: () => deployments,
866
- } as unknown as K8sInit<T, K>;
867
- });
868
-
869
- const expected = true;
870
- const result = await namespaceDeploymentsReady();
871
- expect(result).toBe(expected);
872
- });
873
-
874
- test("should call checkDeploymentStatus if any deployments are not ready", async () => {
875
- const deployments = {
876
- items: [
877
- {
878
- metadata: {
879
- name: "watcher",
880
- namespace: "pepr-system",
881
- },
882
- spec: {
883
- replicas: 1,
884
- },
885
- status: {
886
- readyReplicas: 1,
887
- },
888
- },
889
- {
890
- metadata: {
891
- name: "admission",
892
- namespace: "pepr-system",
893
- },
894
- spec: {
895
- replicas: 2,
896
- },
897
- status: {
898
- readyReplicas: 1,
899
- },
900
- },
901
- ],
902
- };
903
-
904
- const deployments2 = {
905
- items: [
906
- {
907
- metadata: {
908
- name: "watcher",
909
- namespace: "pepr-system",
910
- },
911
- spec: {
912
- replicas: 1,
913
- },
914
- status: {
915
- readyReplicas: 1,
916
- },
917
- },
918
- {
919
- metadata: {
920
- name: "admission",
921
- namespace: "pepr-system",
922
- },
923
- spec: {
924
- replicas: 2,
925
- },
926
- status: {
927
- readyReplicas: 2,
928
- },
929
- },
930
- ],
931
- };
932
-
933
- mockK8s
934
- .mockImplementation(<T extends GenericClass, K extends KubernetesObject>() => {
935
- return {
936
- InNamespace: jest.fn().mockReturnThis(),
937
- Get: () => deployments,
938
- } as unknown as K8sInit<T, K>;
939
- })
940
- .mockImplementation(<T extends GenericClass, K extends KubernetesObject>() => {
941
- return {
942
- InNamespace: jest.fn().mockReturnThis(),
943
- Get: () => deployments2,
944
- } as unknown as K8sInit<T, K>;
945
- });
946
-
947
- const expected = true;
948
- const result = await namespaceDeploymentsReady();
949
-
950
- expect(result).toBe(expected);
951
-
952
- expect(mockK8s).toHaveBeenCalledTimes(1);
953
- });
954
- });
955
-
956
- describe("parseTimeout", () => {
957
- const PREV = "a";
958
- test("should return a number when a valid string number between 1 and 30 is provided", () => {
959
- expect(parseTimeout("5", PREV)).toBe(5);
960
- expect(parseTimeout("1", PREV)).toBe(1);
961
- expect(parseTimeout("30", PREV)).toBe(30);
962
- });
963
-
964
- test("should throw an InvalidArgumentError for non-numeric strings", () => {
965
- expect(() => parseTimeout("abc", PREV)).toThrow(Error);
966
- expect(() => parseTimeout("", PREV)).toThrow(Error);
967
- });
968
-
969
- test("should throw an InvalidArgumentError for numbers outside the 1-30 range", () => {
970
- expect(() => parseTimeout("0", PREV)).toThrow(Error);
971
- expect(() => parseTimeout("31", PREV)).toThrow(Error);
972
- });
973
-
974
- test("should throw an InvalidArgumentError for numeric strings that represent floating point numbers", () => {
975
- expect(() => parseTimeout("5.5", PREV)).toThrow(Error);
976
- expect(() => parseTimeout("20.1", PREV)).toThrow(Error);
977
- });
978
- });
979
-
980
- describe("secretOverLimit", () => {
981
- test("should return true for a string larger than 1MiB", () => {
982
- const largeString = "a".repeat(1048577);
983
- expect(secretOverLimit(largeString)).toBe(true);
984
- });
985
-
986
- test("should return false for a string smaller than 1MiB", () => {
987
- const smallString = "a".repeat(1048575);
988
- expect(secretOverLimit(smallString)).toBe(false);
989
- });
990
- });
991
-
992
- describe("dedent", () => {
993
- test("removes leading spaces based on the smallest indentation", () => {
994
- const input = `
995
- kind: Namespace
996
- metadata:
997
- name: pepr-system
998
- `;
999
- const inputArray = dedent(input).split(/\r?\n/);
1000
-
1001
- expect(inputArray[0]).toBe("kind: Namespace");
1002
- expect(inputArray[1]).toBe("metadata:");
1003
- expect(inputArray[2]).toBe(" name: pepr-system");
1004
- });
1005
-
1006
- test("does not remove internal spacing of lines", () => {
1007
- const input = `kind: ->>> Namespace`;
1008
-
1009
- expect(dedent(input)).toBe("kind: ->>> Namespace");
1010
- });
1011
-
1012
- test("handles strings without leading whitespace consistently", () => {
1013
- const input = `kind: Namespace
1014
- metadata:`;
1015
-
1016
- const inputArray = dedent(input).split(/\r?\n/);
1017
- expect(inputArray[0]).toBe("kind: Namespace");
1018
- expect(inputArray[1]).toBe("metadata:");
1019
- });
1020
-
1021
- test("handles empty strings without crashing", () => {
1022
- const input = ``;
1023
- const expected = ``;
1024
- expect(dedent(input)).toBe(expected);
1025
- });
1026
- });
1027
-
1028
- describe("replaceString", () => {
1029
- test("replaces single instance of a string", () => {
1030
- const original = "Hello, world!";
1031
- const stringA = "world";
1032
- const stringB = "Jest";
1033
- const expected = "Hello, Jest!";
1034
- expect(replaceString(original, stringA, stringB)).toBe(expected);
1035
- });
1036
-
1037
- test("replaces multiple instances of a string", () => {
1038
- const original = "Repeat, repeat, repeat";
1039
- const stringA = "repeat";
1040
- const stringB = "done";
1041
- const expected = "Repeat, done, done";
1042
- expect(replaceString(original, stringA, stringB)).toBe(expected);
1043
- });
1044
-
1045
- test("does nothing if string to replace is not found", () => {
1046
- const original = "Nothing changes here";
1047
- const stringA = "absent";
1048
- const stringB = "present";
1049
- const expected = "Nothing changes here";
1050
- expect(replaceString(original, stringA, stringB)).toBe(expected);
1051
- });
1052
-
1053
- test("escapes special regex characters in string to be replaced", () => {
1054
- const original = "Find the period.";
1055
- const stringA = ".";
1056
- const stringB = "!";
1057
- const expected = "Find the period!";
1058
- expect(replaceString(original, stringA, stringB)).toBe(expected);
1059
- });
1060
-
1061
- test("replaces string with empty string if stringB is empty", () => {
1062
- const original = "Remove this part.";
1063
- const stringA = " this part";
1064
- const stringB = "";
1065
- const expected = "Remove.";
1066
- expect(replaceString(original, stringA, stringB)).toBe(expected);
1067
- });
1068
- });
1069
-
1070
- describe("filterNoMatchReason", () => {
1071
- test("returns regex namespace filter error for Pods whos namespace does not match the regex", () => {
1072
- const binding = {
1073
- kind: { kind: "Pod" },
1074
- filters: { regexNamespaces: ["(.*)-system"], namespaces: [] },
1075
- };
1076
- const obj = { metadata: { namespace: "pepr-demo" } };
1077
- const objArray = [
1078
- { ...obj },
1079
- { ...obj, metadata: { namespace: "pepr-uds" } },
1080
- { ...obj, metadata: { namespace: "pepr-core" } },
1081
- { ...obj, metadata: { namespace: "uds-ns" } },
1082
- { ...obj, metadata: { namespace: "uds" } },
1083
- ];
1084
- const capabilityNamespaces: string[] = [];
1085
- objArray.map(object => {
1086
- const result = filterNoMatchReason(
1087
- binding as unknown as Partial<Binding>,
1088
- object as unknown as Partial<KubernetesObject>,
1089
- capabilityNamespaces,
1090
- );
1091
- expect(result).toEqual(
1092
- `Ignoring Watch Callback: Binding defines namespace regexes '["(.*)-system"]' but Object carries '${object?.metadata?.namespace}'.`,
1093
- );
1094
- });
1095
- });
1096
-
1097
- test("returns no regex namespace filter error for Pods whos namespace does match the regex", () => {
1098
- const binding = {
1099
- kind: { kind: "Pod" },
1100
- filters: { regexNamespaces: [/(.*)-system/], namespaces: [] },
1101
- };
1102
- const obj = { metadata: { namespace: "pepr-demo" } };
1103
- const objArray = [
1104
- { ...obj, metadata: { namespace: "pepr-system" } },
1105
- { ...obj, metadata: { namespace: "pepr-uds-system" } },
1106
- { ...obj, metadata: { namespace: "uds-system" } },
1107
- { ...obj, metadata: { namespace: "some-thing-that-is-a-system" } },
1108
- { ...obj, metadata: { namespace: "your-system" } },
1109
- ];
1110
- const capabilityNamespaces: string[] = [];
1111
- objArray.map(object => {
1112
- const result = filterNoMatchReason(
1113
- binding as unknown as Partial<Binding>,
1114
- object as unknown as Partial<KubernetesObject>,
1115
- capabilityNamespaces,
1116
- );
1117
- expect(result).toEqual(``);
1118
- });
1119
- });
1120
-
1121
- // Names Fail
1122
- test("returns regex name filter error for Pods whos name does not match the regex", () => {
1123
- const binding = {
1124
- kind: { kind: "Pod" },
1125
- filters: { regexName: "^system", namespaces: [] },
1126
- };
1127
- const obj = { metadata: { name: "pepr-demo" } };
1128
- const objArray = [
1129
- { ...obj },
1130
- { ...obj, metadata: { name: "pepr-uds" } },
1131
- { ...obj, metadata: { name: "pepr-core" } },
1132
- { ...obj, metadata: { name: "uds-ns" } },
1133
- { ...obj, metadata: { name: "uds" } },
1134
- ];
1135
- const capabilityNamespaces: string[] = [];
1136
- objArray.map(object => {
1137
- const result = filterNoMatchReason(
1138
- binding as unknown as Partial<Binding>,
1139
- object as unknown as Partial<KubernetesObject>,
1140
- capabilityNamespaces,
1141
- );
1142
- expect(result).toEqual(
1143
- `Ignoring Watch Callback: Binding defines name regex '^system' but Object carries '${object?.metadata?.name}'.`,
1144
- );
1145
- });
1146
- });
1147
-
1148
- // Names Pass
1149
- test("returns no regex name filter error for Pods whos name does match the regex", () => {
1150
- const binding = {
1151
- kind: { kind: "Pod" },
1152
- filters: { regexName: /^system/, namespaces: [] },
1153
- };
1154
- const obj = { metadata: { name: "pepr-demo" } };
1155
- const objArray = [
1156
- { ...obj, metadata: { name: "systemd" } },
1157
- { ...obj, metadata: { name: "systemic" } },
1158
- { ...obj, metadata: { name: "system-of-kube-apiserver" } },
1159
- { ...obj, metadata: { name: "system" } },
1160
- { ...obj, metadata: { name: "system-uds" } },
1161
- ];
1162
- const capabilityNamespaces: string[] = [];
1163
- objArray.map(object => {
1164
- const result = filterNoMatchReason(
1165
- binding as unknown as Partial<Binding>,
1166
- object as unknown as Partial<KubernetesObject>,
1167
- capabilityNamespaces,
1168
- );
1169
- expect(result).toEqual(``);
1170
- });
1171
- });
1172
-
1173
- test("returns namespace filter error for namespace objects with namespace filters", () => {
1174
- const binding = {
1175
- kind: { kind: "Namespace" },
1176
- filters: { namespaces: ["ns1"] },
1177
- };
1178
- const obj = {};
1179
- const capabilityNamespaces: string[] = [];
1180
- const result = filterNoMatchReason(
1181
- binding as unknown as Partial<Binding>,
1182
- obj as unknown as Partial<KubernetesObject>,
1183
- capabilityNamespaces,
1184
- );
1185
- expect(result).toEqual("Ignoring Watch Callback: Cannot use namespace filter on a namespace object.");
1186
- });
1187
-
1188
- test("return an Ignoring Watch Callback string if the binding name and object name are different", () => {
1189
- const binding = {
1190
- filters: { name: "pepr" },
1191
- };
1192
- const obj = {
1193
- metadata: {
1194
- name: "not-pepr",
1195
- },
1196
- };
1197
- const capabilityNamespaces: string[] = [];
1198
- const result = filterNoMatchReason(
1199
- binding as unknown as Partial<Binding>,
1200
- obj as unknown as Partial<KubernetesObject>,
1201
- capabilityNamespaces,
1202
- );
1203
- expect(result).toEqual(`Ignoring Watch Callback: Binding defines name 'pepr' but Object carries 'not-pepr'.`);
1204
- });
1205
- test("returns no Ignoring Watch Callback string if the binding name and object name are the same", () => {
1206
- const binding = {
1207
- filters: { name: "pepr" },
1208
- };
1209
- const obj = {
1210
- metadata: { name: "pepr" },
1211
- };
1212
- const capabilityNamespaces: string[] = [];
1213
- const result = filterNoMatchReason(
1214
- binding as unknown as Partial<Binding>,
1215
- obj as unknown as Partial<KubernetesObject>,
1216
- capabilityNamespaces,
1217
- );
1218
- expect(result).toEqual("");
1219
- });
1220
-
1221
- test("return deletionTimestamp error when there is no deletionTimestamp in the object", () => {
1222
- const binding = {
1223
- filters: { deletionTimestamp: true },
1224
- };
1225
- const obj = {
1226
- metadata: {},
1227
- };
1228
- const capabilityNamespaces: string[] = [];
1229
- const result = filterNoMatchReason(
1230
- binding as unknown as Partial<Binding>,
1231
- obj as unknown as Partial<KubernetesObject>,
1232
- capabilityNamespaces,
1233
- );
1234
- expect(result).toEqual("Ignoring Watch Callback: Binding defines deletionTimestamp but Object does not carry it.");
1235
- });
1236
-
1237
- test("return no deletionTimestamp error when there is a deletionTimestamp in the object", () => {
1238
- const binding = {
1239
- filters: { deletionTimestamp: true },
1240
- };
1241
- const obj = {
1242
- metadata: {
1243
- deletionTimestamp: "2021-01-01T00:00:00Z",
1244
- },
1245
- };
1246
- const capabilityNamespaces: string[] = [];
1247
- const result = filterNoMatchReason(
1248
- binding as unknown as Partial<Binding>,
1249
- obj as unknown as Partial<KubernetesObject>,
1250
- capabilityNamespaces,
1251
- );
1252
- expect(result).not.toEqual("Ignoring Watch Callback: Binding defines deletionTimestamp Object does not carry it.");
1253
- });
1254
-
1255
- test("returns label overlap error when there is no overlap between binding and object labels", () => {
1256
- const binding = {
1257
- filters: { labels: { key: "value" } },
1258
- };
1259
- const obj = {
1260
- metadata: { labels: { anotherKey: "anotherValue" } },
1261
- };
1262
- const capabilityNamespaces: string[] = [];
1263
- const result = filterNoMatchReason(
1264
- binding as unknown as Partial<Binding>,
1265
- obj as unknown as Partial<KubernetesObject>,
1266
- capabilityNamespaces,
1267
- );
1268
- expect(result).toEqual(
1269
- `Ignoring Watch Callback: Binding defines labels '{"key":"value"}' but Object carries '{"anotherKey":"anotherValue"}'.`,
1270
- );
1271
- });
1272
-
1273
- test("returns annotation overlap error when there is no overlap between binding and object annotations", () => {
1274
- const binding = {
1275
- filters: { annotations: { key: "value" } },
1276
- };
1277
- const obj = {
1278
- metadata: { annotations: { anotherKey: "anotherValue" } },
1279
- };
1280
- const capabilityNamespaces: string[] = [];
1281
- const result = filterNoMatchReason(
1282
- binding as unknown as Partial<Binding>,
1283
- obj as unknown as Partial<KubernetesObject>,
1284
- capabilityNamespaces,
1285
- );
1286
- expect(result).toEqual(
1287
- `Ignoring Watch Callback: Binding defines annotations '{"key":"value"}' but Object carries '{"anotherKey":"anotherValue"}'.`,
1288
- );
1289
- });
1290
-
1291
- test("returns capability namespace error when object is not in capability namespaces", () => {
1292
- const binding = {
1293
- model: kind.Pod,
1294
- event: Event.Any,
1295
- kind: {
1296
- group: "",
1297
- version: "v1",
1298
- kind: "Pod",
1299
- },
1300
- filters: {
1301
- name: "bleh",
1302
- namespaces: [],
1303
- regexNamespaces: [],
1304
- regexName: "",
1305
- labels: {},
1306
- annotations: {},
1307
- deletionTimestamp: false,
1308
- },
1309
- callback,
1310
- };
1311
-
1312
- const obj = {
1313
- metadata: { namespace: "ns2", name: "bleh" },
1314
- };
1315
- const capabilityNamespaces = ["ns1"];
1316
- const result = filterNoMatchReason(
1317
- binding as Binding,
1318
- obj as unknown as Partial<KubernetesObject>,
1319
- capabilityNamespaces,
1320
- );
1321
- expect(result).toEqual(
1322
- `Ignoring Watch Callback: Object carries namespace 'ns2' but namespaces allowed by Capability are '["ns1"]'.`,
1323
- );
1324
- });
1325
-
1326
- test("returns binding namespace error when filter namespace is not part of capability namespaces", () => {
1327
- const binding = {
1328
- filters: { namespaces: ["ns3"], regexNamespaces: [] },
1329
- };
1330
- const obj = {};
1331
- const capabilityNamespaces = ["ns1", "ns2"];
1332
- const result = filterNoMatchReason(
1333
- binding as unknown as Partial<Binding>,
1334
- obj as unknown as Partial<KubernetesObject>,
1335
- capabilityNamespaces,
1336
- );
1337
- expect(result).toEqual(
1338
- `Ignoring Watch Callback: Binding defines namespaces ["ns3"] but namespaces allowed by Capability are '["ns1","ns2"]'.`,
1339
- );
1340
- });
1341
-
1342
- test("returns binding and object namespace error when they do not overlap", () => {
1343
- const binding = {
1344
- filters: { namespaces: ["ns1"], regexNamespaces: [] },
1345
- };
1346
- const obj = {
1347
- metadata: { namespace: "ns2" },
1348
- };
1349
- const capabilityNamespaces = ["ns1", "ns2"];
1350
- const result = filterNoMatchReason(
1351
- binding as unknown as Partial<Binding>,
1352
- obj as unknown as Partial<KubernetesObject>,
1353
- capabilityNamespaces,
1354
- );
1355
- expect(result).toEqual(`Ignoring Watch Callback: Binding defines namespaces '["ns1"]' but Object carries 'ns2'.`);
1356
- });
1357
-
1358
- test("return watch violation message when object is in an ignored namespace", () => {
1359
- const binding = {
1360
- filters: { namespaces: ["ns3"] },
1361
- };
1362
- const obj = {
1363
- metadata: { namespace: "ns3" },
1364
- };
1365
- const capabilityNamespaces = ["ns3"];
1366
- const ignoredNamespaces = ["ns3"];
1367
- const result = filterNoMatchReason(
1368
- binding as unknown as Partial<Binding>,
1369
- obj as unknown as Partial<KubernetesObject>,
1370
- capabilityNamespaces,
1371
- ignoredNamespaces,
1372
- );
1373
- expect(result).toEqual(
1374
- `Ignoring Watch Callback: Object carries namespace 'ns3' but ignored namespaces include '["ns3"]'.`,
1375
- );
1376
- });
1377
-
1378
- test("returns empty string when all checks pass", () => {
1379
- const binding = {
1380
- filters: { namespaces: ["ns1"], labels: { key: "value" }, annotations: { key: "value" } },
1381
- };
1382
- const obj = {
1383
- metadata: { namespace: "ns1", labels: { key: "value" }, annotations: { key: "value" } },
1384
- };
1385
- const capabilityNamespaces = ["ns1"];
1386
- const result = filterNoMatchReason(
1387
- binding as unknown as Partial<Binding>,
1388
- obj as unknown as Partial<KubernetesObject>,
1389
- capabilityNamespaces,
1390
- );
1391
- expect(result).toEqual("");
1392
- });
1393
- });
1394
-
1395
- describe("validateHash", () => {
1396
- let originalExit: (code?: number) => never;
1397
-
1398
- beforeEach(() => {
1399
- originalExit = process.exit;
1400
- process.exit = jest.fn() as unknown as (code?: number) => never;
1401
- });
1402
-
1403
- afterEach(() => {
1404
- process.exit = originalExit;
1405
- });
1406
- test("should throw ValidationError for invalid hash values", () => {
1407
- // Examples of invalid hashes
1408
- const invalidHashes = [
1409
- "", // Empty string
1410
- "12345", // Too short
1411
- "zxcvbnmasdfghjklqwertyuiop1234567890zxcvbnmasdfghjklqwertyuio", // Contains invalid character 'z'
1412
- "123456789012345678901234567890123456789012345678901234567890123", // 63 characters, one short
1413
- ];
1414
-
1415
- invalidHashes.forEach(hash => {
1416
- expect(() => validateHash(hash)).toThrow(ValidationError);
1417
- });
1418
- });
1419
-
1420
- test("should not throw ValidationError for valid SHA-256 hash", () => {
1421
- // Example of a valid SHA-256 hash
1422
- const validHash = "abc123def456abc123def456abc123def456abc123def456abc123def456abc1";
1423
- expect(() => validateHash(validHash)).not.toThrow();
1424
- });
1425
- });
1426
-
1427
- describe("matchesRegex", () => {
1428
- test("should return true for a valid pattern that matches the string", () => {
1429
- const pattern = /abc/;
1430
- const testString = "abc123";
1431
- const result = matchesRegex(new RegExp(pattern).source, testString);
1432
- expect(result).toBe(true);
1433
- });
1434
-
1435
- test("should return false for a valid pattern that does not match the string", () => {
1436
- const pattern = /xyz/;
1437
- const testString = "abc123";
1438
- const result = matchesRegex(new RegExp(pattern).source, testString);
1439
- expect(result).toBe(false);
1440
- });
1441
-
1442
- test("should return false for an invalid regex pattern", () => {
1443
- const invalidPattern = new RegExp(/^p/); // Invalid regex with unclosed bracket
1444
- const testString = "test";
1445
- const result = matchesRegex(invalidPattern.source, testString);
1446
- expect(result).toBe(false);
1447
- });
1448
-
1449
- test("should return false when pattern is null or undefined", () => {
1450
- const testString = "abc123";
1451
- // Check for undefined
1452
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1453
- expect(matchesRegex(undefined as any, testString)).toBe(false);
1454
- // Check for null
1455
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1456
- expect(matchesRegex(null as any, testString)).toBe(false);
1457
- });
1458
-
1459
- test("should return true for an empty string matching an empty regex", () => {
1460
- const pattern = new RegExp("");
1461
- const testString = "";
1462
- const result = matchesRegex(new RegExp(pattern).source, testString);
1463
- expect(result).toBe(true);
1464
- });
1465
-
1466
- test("should return false for an empty string and a non-empty regex", () => {
1467
- const pattern = new RegExp("abc");
1468
- const testString = "";
1469
- const result = matchesRegex(new RegExp(pattern).source, testString);
1470
- expect(result).toBe(false);
1471
- });
1472
-
1473
- test("should return true for a complex valid regex that matches", () => {
1474
- const pattern = /^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[A-Za-z]+$/;
1475
- const testString = "test@example.com";
1476
- const result = matchesRegex(new RegExp(pattern).source, testString);
1477
- expect(result).toBe(true);
1478
- });
1479
-
1480
- test("should return false for a complex valid regex that does not match", () => {
1481
- const pattern = /^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[A-Za-z]+$/;
1482
- const testString = "invalid-email.com";
1483
- const result = matchesRegex(new RegExp(pattern).source, testString);
1484
- expect(result).toBe(false);
1485
- });
1486
- });