pepr 0.38.0 → 0.38.2

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 (77) hide show
  1. package/dist/cli.js +13 -13
  2. package/dist/controller.js +1 -1
  3. package/package.json +5 -3
  4. package/src/lib/assets/pods.ts +6 -6
  5. package/src/lib/assets/yaml.ts +6 -6
  6. package/dist/cli/init/utils.test.d.ts +0 -2
  7. package/dist/cli/init/utils.test.d.ts.map +0 -1
  8. package/dist/cli/init/walkthrough.test.d.ts +0 -2
  9. package/dist/cli/init/walkthrough.test.d.ts.map +0 -1
  10. package/dist/lib/adjudicators.test.d.ts +0 -2
  11. package/dist/lib/adjudicators.test.d.ts.map +0 -1
  12. package/dist/lib/assets/helm.test.d.ts +0 -2
  13. package/dist/lib/assets/helm.test.d.ts.map +0 -1
  14. package/dist/lib/assets/pods.test.d.ts +0 -2
  15. package/dist/lib/assets/pods.test.d.ts.map +0 -1
  16. package/dist/lib/capability.test.d.ts +0 -2
  17. package/dist/lib/capability.test.d.ts.map +0 -1
  18. package/dist/lib/controller/store.test.d.ts +0 -2
  19. package/dist/lib/controller/store.test.d.ts.map +0 -1
  20. package/dist/lib/errors.test.d.ts +0 -2
  21. package/dist/lib/errors.test.d.ts.map +0 -1
  22. package/dist/lib/filter.test.d.ts +0 -3
  23. package/dist/lib/filter.test.d.ts.map +0 -1
  24. package/dist/lib/finalizer.test.d.ts +0 -2
  25. package/dist/lib/finalizer.test.d.ts.map +0 -1
  26. package/dist/lib/helpers.test.d.ts +0 -2
  27. package/dist/lib/helpers.test.d.ts.map +0 -1
  28. package/dist/lib/included-files.test.d.ts +0 -2
  29. package/dist/lib/included-files.test.d.ts.map +0 -1
  30. package/dist/lib/logger.test.d.ts +0 -2
  31. package/dist/lib/logger.test.d.ts.map +0 -1
  32. package/dist/lib/metrics.test.d.ts +0 -2
  33. package/dist/lib/metrics.test.d.ts.map +0 -1
  34. package/dist/lib/module.test.d.ts +0 -2
  35. package/dist/lib/module.test.d.ts.map +0 -1
  36. package/dist/lib/mutate-request.test.d.ts +0 -2
  37. package/dist/lib/mutate-request.test.d.ts.map +0 -1
  38. package/dist/lib/queue.test.d.ts +0 -2
  39. package/dist/lib/queue.test.d.ts.map +0 -1
  40. package/dist/lib/schedule.test.d.ts +0 -15
  41. package/dist/lib/schedule.test.d.ts.map +0 -1
  42. package/dist/lib/storage.test.d.ts +0 -2
  43. package/dist/lib/storage.test.d.ts.map +0 -1
  44. package/dist/lib/tls.test.d.ts +0 -2
  45. package/dist/lib/tls.test.d.ts.map +0 -1
  46. package/dist/lib/utils.test.d.ts +0 -2
  47. package/dist/lib/utils.test.d.ts.map +0 -1
  48. package/dist/lib/validate-request.test.d.ts +0 -2
  49. package/dist/lib/validate-request.test.d.ts.map +0 -1
  50. package/dist/lib/watch-processor.test.d.ts +0 -2
  51. package/dist/lib/watch-processor.test.d.ts.map +0 -1
  52. package/dist/sdk/sdk.test.d.ts +0 -2
  53. package/dist/sdk/sdk.test.d.ts.map +0 -1
  54. package/src/cli/init/utils.test.ts +0 -19
  55. package/src/cli/init/walkthrough.test.ts +0 -102
  56. package/src/lib/adjudicators.test.ts +0 -1236
  57. package/src/lib/assets/helm.test.ts +0 -64
  58. package/src/lib/assets/pods.test.ts +0 -553
  59. package/src/lib/capability.test.ts +0 -655
  60. package/src/lib/controller/store.test.ts +0 -131
  61. package/src/lib/errors.test.ts +0 -85
  62. package/src/lib/filter.test.ts +0 -691
  63. package/src/lib/finalizer.test.ts +0 -236
  64. package/src/lib/helpers.test.ts +0 -1486
  65. package/src/lib/included-files.test.ts +0 -22
  66. package/src/lib/logger.test.ts +0 -18
  67. package/src/lib/metrics.test.ts +0 -132
  68. package/src/lib/module.test.ts +0 -126
  69. package/src/lib/mutate-request.test.ts +0 -187
  70. package/src/lib/queue.test.ts +0 -152
  71. package/src/lib/schedule.test.ts +0 -217
  72. package/src/lib/storage.test.ts +0 -216
  73. package/src/lib/tls.test.ts +0 -18
  74. package/src/lib/utils.test.ts +0 -69
  75. package/src/lib/validate-request.test.ts +0 -121
  76. package/src/lib/watch-processor.test.ts +0 -417
  77. package/src/sdk/sdk.test.ts +0 -276
@@ -1,417 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
- import { afterAll, beforeEach, describe, expect, it, jest } from "@jest/globals";
4
- import { GenericClass, K8s, KubernetesObject, kind } from "kubernetes-fluent-client";
5
- import { K8sInit, WatchPhase } from "kubernetes-fluent-client/dist/fluent/types";
6
- import { WatchCfg, WatchEvent, Watcher } from "kubernetes-fluent-client/dist/fluent/watch";
7
- import { Capability } from "./capability";
8
- import { setupWatch, logEvent, queueKey, getOrCreateQueue } from "./watch-processor";
9
- import Log from "./logger";
10
- import { metricsCollector } from "./metrics";
11
-
12
- type onCallback = (eventName: string | symbol, listener: (msg: string) => void) => void;
13
-
14
- // Mock the dependencies
15
- jest.mock("kubernetes-fluent-client");
16
-
17
- jest.mock("./logger", () => ({
18
- debug: jest.fn(),
19
- error: jest.fn(),
20
- }));
21
-
22
- jest.mock("./metrics", () => ({
23
- metricsCollector: {
24
- initCacheMissWindow: jest.fn(),
25
- incCacheMiss: jest.fn(),
26
- incRetryCount: jest.fn(),
27
- },
28
- }));
29
- describe("WatchProcessor", () => {
30
- const mockStart = jest.fn();
31
- const mockK8s = jest.mocked(K8s);
32
- const mockApply = jest.fn();
33
- const mockGet = jest.fn();
34
- const mockWatch = jest.fn();
35
- const mockEvents = jest.fn() as jest.MockedFunction<onCallback>;
36
-
37
- const capabilities = [
38
- {
39
- bindings: [
40
- {
41
- isWatch: true,
42
- isQueue: false,
43
- model: "someModel",
44
- filters: {},
45
- event: "Create",
46
- watchCallback: () => {
47
- console.log("words");
48
- },
49
- },
50
- ],
51
- },
52
- ] as unknown as Capability[];
53
-
54
- beforeEach(() => {
55
- jest.resetAllMocks();
56
- jest.useFakeTimers();
57
-
58
- mockK8s.mockImplementation(<T extends GenericClass, K extends KubernetesObject>() => {
59
- return {
60
- Apply: mockApply,
61
- InNamespace: jest.fn().mockReturnThis(),
62
- Watch: mockWatch,
63
- Get: mockGet,
64
- } as unknown as K8sInit<T, K>;
65
- });
66
-
67
- mockWatch.mockImplementation(() => {
68
- return {
69
- start: mockStart,
70
- events: {
71
- on: mockEvents,
72
- },
73
- } as unknown as Watcher<typeof kind.Pod>;
74
- });
75
-
76
- mockGet.mockImplementation(() => ({
77
- data: {
78
- "42dae115ed-8aa1f3": "756",
79
- "8aa1fde099-32a12": "750",
80
- },
81
- }));
82
-
83
- mockApply.mockImplementation(() => Promise.resolve());
84
- });
85
-
86
- it("should setup watches for all bindings with isWatch=true", async () => {
87
- const watchCfg: WatchCfg = {
88
- resyncFailureMax: 5,
89
- resyncDelaySec: 5,
90
- lastSeenLimitSeconds: 300,
91
- relistIntervalSec: 600,
92
- };
93
-
94
- capabilities.push({
95
- bindings: [
96
- {
97
- isWatch: true,
98
- isQueue: true,
99
- model: "someModel",
100
- filters: { name: "bleh" },
101
- event: "Create",
102
- watchCallback: jest.fn(),
103
- },
104
- { isWatch: false, isQueue: false, model: "someModel", filters: {}, event: "Create", watchCallback: jest.fn() },
105
- ],
106
- } as unknown as Capability);
107
-
108
- setupWatch(capabilities);
109
-
110
- expect(mockK8s).toHaveBeenCalledTimes(2);
111
- expect(mockK8s).toHaveBeenNthCalledWith(1, "someModel", {});
112
- expect(mockK8s).toHaveBeenNthCalledWith(2, "someModel", { name: "bleh" });
113
-
114
- expect(mockWatch).toHaveBeenCalledTimes(2);
115
- expect(mockWatch).toHaveBeenCalledWith(expect.any(Function), expect.objectContaining(watchCfg));
116
- });
117
-
118
- it("should not setup watches if capabilities array is empty", async () => {
119
- await setupWatch([]);
120
- expect(mockWatch).toHaveBeenCalledTimes(0);
121
- });
122
-
123
- it("should not setup watches if no bindings are present", async () => {
124
- const capabilities = [{ bindings: [] }, { bindings: [] }] as unknown as Capability[];
125
- await setupWatch(capabilities);
126
- expect(mockWatch).toHaveBeenCalledTimes(0);
127
- });
128
-
129
- it("should exit if the watch fails to start", async () => {
130
- const exitSpy = jest.spyOn(process, "exit").mockImplementation(() => {
131
- return undefined as never;
132
- });
133
-
134
- mockStart.mockRejectedValue(new Error("err") as never);
135
-
136
- await setupWatch(capabilities);
137
-
138
- expect(exitSpy).toHaveBeenCalledWith(1);
139
- });
140
-
141
- it("should watch for the give_up event", async () => {
142
- const exitSpy = jest.spyOn(process, "exit").mockImplementation(() => {
143
- return undefined as never;
144
- });
145
-
146
- mockEvents.mockImplementation((eventName: string | symbol, listener: (msg: string) => void) => {
147
- if (eventName === WatchEvent.GIVE_UP) {
148
- expect(listener).toBeInstanceOf(Function);
149
- listener("err");
150
- expect(exitSpy).toHaveBeenCalledWith(1);
151
- }
152
- });
153
-
154
- setupWatch(capabilities);
155
- });
156
-
157
- it("should setup watches with correct phases for different events", async () => {
158
- const watchCallbackCreate = jest.fn();
159
- const watchCallbackUpdate = jest.fn();
160
- const watchCallbackDelete = jest.fn();
161
-
162
- const capabilities = [
163
- {
164
- bindings: [
165
- { isWatch: true, model: "someModel", filters: {}, event: "Create", watchCallback: watchCallbackCreate },
166
- { isWatch: true, model: "someModel", filters: {}, event: "Update", watchCallback: watchCallbackUpdate },
167
- { isWatch: true, model: "someModel", filters: {}, event: "Delete", watchCallback: watchCallbackDelete },
168
- // Add more events here
169
- ],
170
- },
171
- ] as unknown as Capability[];
172
-
173
- setupWatch(capabilities);
174
-
175
- type mockArg = [(payload: kind.Pod, phase: WatchPhase) => void, WatchCfg];
176
-
177
- const firstCall = mockWatch.mock.calls[0] as unknown as mockArg;
178
- const secondCall = mockWatch.mock.calls[1] as unknown as mockArg;
179
- const thirdCall = mockWatch.mock.calls[2] as unknown as mockArg;
180
-
181
- expect(firstCall[1].resyncFailureMax).toEqual(5);
182
- expect(firstCall[1].resyncDelaySec).toEqual(5);
183
- expect(firstCall[0]).toBeInstanceOf(Function);
184
-
185
- firstCall[0]({} as kind.Pod, WatchPhase.Added);
186
- expect(watchCallbackCreate).toHaveBeenCalledTimes(1);
187
- expect(watchCallbackCreate).toHaveBeenCalledWith({}, WatchPhase.Added);
188
-
189
- firstCall[0]({} as kind.Pod, WatchPhase.Modified);
190
- firstCall[0]({} as kind.Pod, WatchPhase.Deleted);
191
- expect(watchCallbackDelete).toHaveBeenCalledTimes(0);
192
- expect(watchCallbackUpdate).toHaveBeenCalledTimes(0);
193
-
194
- watchCallbackCreate.mockClear();
195
- watchCallbackUpdate.mockClear();
196
- watchCallbackDelete.mockClear();
197
-
198
- secondCall[0]({} as kind.Pod, WatchPhase.Modified);
199
- expect(watchCallbackUpdate).toHaveBeenCalledTimes(1);
200
- expect(watchCallbackUpdate).toHaveBeenCalledWith({}, WatchPhase.Modified);
201
-
202
- secondCall[0]({} as kind.Pod, WatchPhase.Added);
203
- secondCall[0]({} as kind.Pod, WatchPhase.Deleted);
204
- expect(watchCallbackCreate).toHaveBeenCalledTimes(0);
205
- expect(watchCallbackDelete).toHaveBeenCalledTimes(0);
206
-
207
- watchCallbackCreate.mockClear();
208
- watchCallbackUpdate.mockClear();
209
- watchCallbackDelete.mockClear();
210
-
211
- thirdCall[0]({} as kind.Pod, WatchPhase.Deleted);
212
- expect(watchCallbackDelete).toHaveBeenCalledTimes(1);
213
- expect(watchCallbackDelete).toHaveBeenCalledWith({}, WatchPhase.Deleted);
214
-
215
- thirdCall[0]({} as kind.Pod, WatchPhase.Added);
216
- thirdCall[0]({} as kind.Pod, WatchPhase.Modified);
217
- expect(watchCallbackCreate).toHaveBeenCalledTimes(0);
218
- expect(watchCallbackUpdate).toHaveBeenCalledTimes(0);
219
- });
220
-
221
- it("should call the metricsCollector methods on respective events", async () => {
222
- const mockIncCacheMiss = metricsCollector.incCacheMiss;
223
- const mockInitCacheMissWindow = metricsCollector.initCacheMissWindow;
224
- const mockIncRetryCount = metricsCollector.incRetryCount;
225
-
226
- const watchCallback = jest.fn();
227
- const capabilities = [
228
- {
229
- bindings: [{ isWatch: true, model: "someModel", filters: {}, event: "Create", watchCallback: watchCallback }],
230
- },
231
- ] as unknown as Capability[];
232
-
233
- setupWatch(capabilities);
234
-
235
- type mockArg = [(payload: kind.Pod, phase: WatchPhase) => void, WatchCfg];
236
-
237
- const firstCall = mockWatch.mock.calls[0] as unknown as mockArg;
238
-
239
- const cacheMissWindowName = "window-1";
240
- const retryCount = "retry-1";
241
-
242
- firstCall[0]({} as kind.Pod, WatchPhase.Added);
243
- mockEvents.mock.calls.forEach(call => {
244
- if (call[0] === WatchEvent.CACHE_MISS) {
245
- call[1](cacheMissWindowName);
246
- }
247
- if (call[0] === WatchEvent.INIT_CACHE_MISS) {
248
- call[1](cacheMissWindowName);
249
- }
250
- if (call[0] === WatchEvent.INC_RESYNC_FAILURE_COUNT) {
251
- call[1](retryCount);
252
- }
253
- });
254
-
255
- expect(mockIncCacheMiss).toHaveBeenCalledWith(cacheMissWindowName);
256
- expect(mockInitCacheMissWindow).toHaveBeenCalledWith(cacheMissWindowName);
257
- expect(mockIncRetryCount).toHaveBeenCalledWith(retryCount);
258
- });
259
-
260
- it("should call parseInt with process.env.PEPR_RELIST_INTERVAL_SECONDS", async () => {
261
- const parseIntSpy = jest.spyOn(global, "parseInt");
262
-
263
- process.env.PEPR_RELIST_INTERVAL_SECONDS = "1800";
264
- process.env.PEPR_LAST_SEEN_LIMIT_SECONDS = "300";
265
- process.env.PEPR_RESYNC_DELAY_SECONDS = "60";
266
- process.env.PEPR_RESYNC_FAILURE_MAX = "5";
267
-
268
- /* eslint-disable @typescript-eslint/no-unused-vars */
269
- const watchCfg: WatchCfg = {
270
- resyncFailureMax: parseInt(process.env.PEPR_RESYNC_FAILURE_MAX, 10),
271
- resyncDelaySec: parseInt(process.env.PEPR_RESYNC_DELAY_SECONDS, 10),
272
- lastSeenLimitSeconds: parseInt(process.env.PEPR_LAST_SEEN_LIMIT_SECONDS, 10),
273
- relistIntervalSec: parseInt(process.env.PEPR_RELIST_INTERVAL_SECONDS, 10),
274
- };
275
-
276
- capabilities.push({
277
- bindings: [
278
- { isWatch: true, model: "someModel", filters: { name: "bleh" }, event: "Create", watchCallback: jest.fn() },
279
- { isWatch: false, model: "someModel", filters: {}, event: "Create", watchCallback: jest.fn() },
280
- ],
281
- } as unknown as Capability);
282
-
283
- setupWatch(capabilities);
284
-
285
- expect(parseIntSpy).toHaveBeenCalledWith("1800", 10);
286
- expect(parseIntSpy).toHaveBeenCalledWith("300", 10);
287
- expect(parseIntSpy).toHaveBeenCalledWith("60", 10);
288
- expect(parseIntSpy).toHaveBeenCalledWith("5", 10);
289
- parseIntSpy.mockRestore();
290
- });
291
- });
292
-
293
- describe("logEvent function", () => {
294
- it("should handle data events", () => {
295
- const mockObj = { id: "123", type: "Pod" } as KubernetesObject;
296
- const message = "Test message";
297
- logEvent(WatchEvent.DATA, message, mockObj);
298
- expect(Log.debug).toHaveBeenCalledWith(mockObj, `Watch event ${WatchEvent.DATA} received. ${message}.`);
299
- });
300
-
301
- it("should handle CONNECT events", () => {
302
- const url = "/api/v1/namespaces/default/pods?watch=true&resourceVersion=0";
303
- logEvent(WatchEvent.CONNECT, url);
304
- expect(Log.debug).toHaveBeenCalledWith(`Watch event ${WatchEvent.CONNECT} received. ${url}.`);
305
- });
306
-
307
- it("should handle LIST_ERROR events", () => {
308
- const message = "LIST_ERROR";
309
- logEvent(WatchEvent.LIST_ERROR, message);
310
- expect(Log.debug).toHaveBeenCalledWith(`Watch event ${WatchEvent.LIST_ERROR} received. ${message}.`);
311
- });
312
- it("should handle LIST events", () => {
313
- const podList = {
314
- kind: "PodList",
315
- apiVersion: "v1",
316
- metadata: { resourceVersion: "10245" },
317
- items: [],
318
- };
319
- const message = JSON.stringify(podList, undefined, 2);
320
- logEvent(WatchEvent.LIST, message);
321
- expect(Log.debug).toHaveBeenCalledWith(`Watch event ${WatchEvent.LIST} received. ${message}.`);
322
- });
323
-
324
- it("should handle DATA_ERROR events", () => {
325
- const message = "Test message";
326
- logEvent(WatchEvent.DATA_ERROR, message);
327
- expect(Log.debug).toHaveBeenCalledWith(`Watch event ${WatchEvent.DATA_ERROR} received. ${message}.`);
328
- });
329
- });
330
-
331
- describe("queueKey", () => {
332
- const withKindNsName = { kind: "Pod", metadata: { namespace: "my-ns", name: "my-name" } } as KubernetesObject;
333
- const withKindNs = { kind: "Pod", metadata: { namespace: "my-ns" } } as KubernetesObject;
334
- const withKindName = { kind: "Pod", metadata: { name: "my-name" } } as KubernetesObject;
335
- const withNsName = { metadata: { namespace: "my-ns", name: "my-name" } } as KubernetesObject;
336
- const withKind = { kind: "Pod" } as KubernetesObject;
337
- const withNs = { metadata: { namespace: "my-ns" } } as KubernetesObject;
338
- const withName = { metadata: { name: "my-name" } } as KubernetesObject;
339
- const withNone = {} as KubernetesObject;
340
-
341
- const original = process.env.PEPR_RECONCILE_STRATEGY;
342
-
343
- it.each([
344
- ["kind", withKindNsName, "Pod"],
345
- ["kind", withKindNs, "Pod"],
346
- ["kind", withKindName, "Pod"],
347
- ["kind", withNsName, "UnknownKind"],
348
- ["kind", withKind, "Pod"],
349
- ["kind", withNs, "UnknownKind"],
350
- ["kind", withName, "UnknownKind"],
351
- ["kind", withNone, "UnknownKind"],
352
- ["kindNs", withKindNsName, "Pod/my-ns"],
353
- ["kindNs", withKindNs, "Pod/my-ns"],
354
- ["kindNs", withKindName, "Pod/cluster-scoped"],
355
- ["kindNs", withNsName, "UnknownKind/my-ns"],
356
- ["kindNs", withKind, "Pod/cluster-scoped"],
357
- ["kindNs", withNs, "UnknownKind/my-ns"],
358
- ["kindNs", withName, "UnknownKind/cluster-scoped"],
359
- ["kindNs", withNone, "UnknownKind/cluster-scoped"],
360
- ["kindNsName", withKindNsName, "Pod/my-ns/my-name"],
361
- ["kindNsName", withKindNs, "Pod/my-ns/Unnamed"],
362
- ["kindNsName", withKindName, "Pod/cluster-scoped/my-name"],
363
- ["kindNsName", withNsName, "UnknownKind/my-ns/my-name"],
364
- ["kindNsName", withKind, "Pod/cluster-scoped/Unnamed"],
365
- ["kindNsName", withNs, "UnknownKind/my-ns/Unnamed"],
366
- ["kindNsName", withName, "UnknownKind/cluster-scoped/my-name"],
367
- ["kindNsName", withNone, "UnknownKind/cluster-scoped/Unnamed"],
368
- ["global", withKindNsName, "global"],
369
- ["global", withKindNs, "global"],
370
- ["global", withKindName, "global"],
371
- ["global", withNsName, "global"],
372
- ["global", withKind, "global"],
373
- ["global", withNs, "global"],
374
- ["global", withName, "global"],
375
- ["global", withNone, "global"],
376
- ])("PEPR_RECONCILE_STRATEGY='%s' over '%j' becomes '%s'", (strat, obj, key) => {
377
- process.env.PEPR_RECONCILE_STRATEGY = strat;
378
- expect(queueKey(obj)).toBe(key);
379
- });
380
-
381
- afterAll(() => {
382
- process.env.PEPR_RECONCILE_STRATEGY = original;
383
- });
384
- });
385
-
386
- describe("getOrCreateQueue", () => {
387
- it("creates a Queue instance on first call", () => {
388
- const obj: KubernetesObject = {
389
- kind: "queue",
390
- metadata: {
391
- name: "nm",
392
- namespace: "ns",
393
- },
394
- };
395
-
396
- const firstQueue = getOrCreateQueue(obj);
397
- expect(firstQueue.label()).toBeDefined();
398
- });
399
-
400
- it("returns same Queue instance on subsequent calls", () => {
401
- const obj: KubernetesObject = {
402
- kind: "queue",
403
- metadata: {
404
- name: "nm",
405
- namespace: "ns",
406
- },
407
- };
408
-
409
- const firstQueue = getOrCreateQueue(obj);
410
- expect(firstQueue.label()).toBeDefined();
411
-
412
- const secondQueue = getOrCreateQueue(obj);
413
- expect(secondQueue.label()).toBeDefined();
414
-
415
- expect(firstQueue).toBe(secondQueue);
416
- });
417
- });
@@ -1,276 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
-
4
- import { expect, test } from "@jest/globals";
5
- import { PeprValidateRequest } from "../lib/validate-request";
6
- import { PeprMutateRequest } from "../lib/mutate-request";
7
- import { a } from "../lib";
8
- import { containers, writeEvent, getOwnerRefFrom, sanitizeResourceName } from "./sdk";
9
- import * as fc from "fast-check";
10
- import { beforeEach, describe, it, jest } from "@jest/globals";
11
- import { GenericKind } from "kubernetes-fluent-client";
12
- import { K8s, kind } from "kubernetes-fluent-client";
13
- import { Mock } from "jest-mock";
14
- import { V1OwnerReference } from "@kubernetes/client-node";
15
-
16
- jest.mock("kubernetes-fluent-client", () => ({
17
- K8s: jest.fn(),
18
- Log: {
19
- debug: jest.fn(),
20
- warn: jest.fn(),
21
- error: jest.fn(),
22
- },
23
- kind: {
24
- CoreEvent: "CoreEvent",
25
- },
26
- }));
27
-
28
- describe("containers", () => {
29
- test("should return a list of containers in the pod when in a validate block", async () => {
30
- const standardContainers = [
31
- {
32
- name: "container-1",
33
- },
34
- ];
35
- const initContainers = [
36
- {
37
- name: "init-container-1",
38
- },
39
- ];
40
- const ephemeralContainers = [
41
- {
42
- name: "ephemeral-container-1",
43
- },
44
- ];
45
- const allContainers = [...standardContainers, ...initContainers, ...ephemeralContainers];
46
- const peprValidationRequest = {
47
- Raw: {
48
- spec: {
49
- containers: standardContainers,
50
- initContainers,
51
- ephemeralContainers,
52
- },
53
- },
54
- } as PeprValidateRequest<a.Pod>;
55
-
56
- let result = containers(peprValidationRequest);
57
- expect(result).toEqual(expect.arrayContaining(allContainers));
58
- expect(result).toHaveLength(allContainers.length);
59
-
60
- result = containers(peprValidationRequest, "containers");
61
- expect(result).toEqual(expect.arrayContaining(standardContainers));
62
- expect(result).toHaveLength(standardContainers.length);
63
-
64
- result = containers(peprValidationRequest, "initContainers");
65
- expect(result).toEqual(expect.arrayContaining(initContainers));
66
- expect(result).toHaveLength(initContainers.length);
67
-
68
- result = containers(peprValidationRequest, "ephemeralContainers");
69
- expect(result).toEqual(expect.arrayContaining(ephemeralContainers));
70
- expect(result).toHaveLength(ephemeralContainers.length);
71
- });
72
-
73
- test("should return a list of containers in the pod when in a mutate block", async () => {
74
- const standardContainers = [
75
- {
76
- name: "container-1",
77
- },
78
- ];
79
- const initContainers = [
80
- {
81
- name: "init-container-1",
82
- },
83
- ];
84
- const ephemeralContainers = [
85
- {
86
- name: "ephemeral-container-1",
87
- },
88
- ];
89
- const allContainers = [...standardContainers, ...initContainers, ...ephemeralContainers];
90
- const peprMutateRequest = {
91
- Raw: {
92
- spec: {
93
- containers: standardContainers,
94
- initContainers,
95
- ephemeralContainers,
96
- },
97
- },
98
- } as PeprMutateRequest<a.Pod>;
99
-
100
- let result = containers(peprMutateRequest);
101
- expect(result).toEqual(expect.arrayContaining(allContainers));
102
- expect(result).toHaveLength(allContainers.length);
103
-
104
- result = containers(peprMutateRequest, "containers");
105
- expect(result).toEqual(expect.arrayContaining(standardContainers));
106
- expect(result).toHaveLength(standardContainers.length);
107
-
108
- result = containers(peprMutateRequest, "initContainers");
109
- expect(result).toEqual(expect.arrayContaining(initContainers));
110
- expect(result).toHaveLength(initContainers.length);
111
-
112
- result = containers(peprMutateRequest, "ephemeralContainers");
113
- expect(result).toEqual(expect.arrayContaining(ephemeralContainers));
114
- expect(result).toHaveLength(ephemeralContainers.length);
115
- });
116
- });
117
-
118
- describe("writeEvent", () => {
119
- let Create: Mock;
120
- beforeEach(() => {
121
- jest.clearAllMocks();
122
-
123
- Create = jest.fn();
124
-
125
- (K8s as jest.Mock).mockImplementation(() => ({
126
- Create,
127
- PatchStatus: jest.fn(),
128
- }));
129
- });
130
-
131
- it("should write a K8s event for the CRD", async () => {
132
- const cr = {
133
- apiVersion: "v1",
134
- kind: "Package",
135
- metadata: { name: "test", namespace: "default", uid: "1" },
136
- };
137
- const event = { message: "Test event" };
138
- await writeEvent(
139
- cr as GenericKind,
140
- event,
141
- "Warning",
142
- "ReconciliationFailed",
143
- "uds.dev/operator",
144
- process.env.HOSTNAME as string,
145
- );
146
- expect(K8s).toHaveBeenCalledWith(kind.CoreEvent);
147
- expect(Create).toHaveBeenCalledWith({
148
- ...event,
149
- type: "Warning",
150
- reason: "ReconciliationFailed",
151
- metadata: { namespace: "default", generateName: "test" },
152
- involvedObject: {
153
- apiVersion: "v1",
154
- kind: "Package",
155
- name: "test",
156
- namespace: "default",
157
- uid: "1",
158
- },
159
- firstTimestamp: expect.any(Date),
160
- reportingComponent: "uds.dev/operator",
161
- reportingInstance: process.env.HOSTNAME,
162
- });
163
- });
164
- });
165
-
166
- describe("getOwnerRefFrom", () => {
167
- const customResource = {
168
- apiVersion: "v1",
169
- kind: "Package",
170
- metadata: { name: "test", namespace: "default", uid: "1" },
171
- };
172
-
173
- const ownerRef = [
174
- {
175
- apiVersion: "v1",
176
- kind: "Package",
177
- name: "test",
178
- uid: "1",
179
- },
180
- ];
181
-
182
- const ownerRefWithController = ownerRef.map(item => ({
183
- ...item,
184
- controller: true,
185
- }));
186
- const ownerRefWithBlockOwnerDeletion = ownerRef.map(item => ({
187
- ...item,
188
- blockOwnerDeletion: false,
189
- }));
190
- const ownerRefWithAllFields = ownerRef.map(item => ({
191
- ...item,
192
- blockOwnerDeletion: true,
193
- controller: false,
194
- }));
195
-
196
- test.each([
197
- [true, false, ownerRefWithAllFields],
198
- [false, undefined, ownerRefWithBlockOwnerDeletion],
199
- [undefined, true, ownerRefWithController],
200
- [undefined, undefined, ownerRef],
201
- ])(
202
- "should return owner reference for the CRD for combinations of V1OwnerReference fields - Optionals: blockOwnerDeletion (%s), controller (%s)",
203
- (blockOwnerDeletion, controller, expected) => {
204
- const result = getOwnerRefFrom(customResource, blockOwnerDeletion, controller);
205
- expect(result).toStrictEqual(expected);
206
- },
207
- );
208
-
209
- it("should support all defined fields in the V1OwnerReference type", () => {
210
- const V1OwnerReferenceFieldCount = Object.getOwnPropertyNames(V1OwnerReference).length;
211
- const result = getOwnerRefFrom(customResource, false, true);
212
- expect(Object.keys(result[0]).length).toEqual(V1OwnerReferenceFieldCount);
213
- });
214
- });
215
-
216
- describe("sanitizeResourceName Fuzzing Tests", () => {
217
- test("should handle any random string input", () => {
218
- fc.assert(
219
- fc.property(fc.string(), name => {
220
- expect(() => sanitizeResourceName(name)).not.toThrow();
221
- const sanitized = sanitizeResourceName(name);
222
- expect(typeof sanitized).toBe("string");
223
- }),
224
- );
225
- });
226
- });
227
-
228
- describe("sanitizeResourceName Property-Based Tests", () => {
229
- test("should always return lowercase, alphanumeric names without leading/trailing hyphens", () => {
230
- fc.assert(
231
- fc.property(fc.string(), name => {
232
- const sanitized = sanitizeResourceName(name);
233
- if (sanitized.length > 0) {
234
- expect(sanitized).toMatch(/^[a-z0-9]+(?:-[a-z0-9]+)*$/);
235
- }
236
- expect(sanitized).toBe(sanitized.toLowerCase());
237
- expect(sanitized.length).toBeLessThanOrEqual(250);
238
- }),
239
- );
240
- });
241
- });
242
-
243
- describe("sanitizeResourceName", () => {
244
- it("should return same resource name if no sanitization needed", () => {
245
- const resourceName = "test-resource";
246
- const sanitizedResourceName = sanitizeResourceName(resourceName);
247
- expect(sanitizedResourceName).toEqual("test-resource");
248
- });
249
-
250
- it("should replace capital letters with lowercase letters", () => {
251
- const resourceName = "Test-ResourCe";
252
- const sanitizedResourceName = sanitizeResourceName(resourceName);
253
- expect(sanitizedResourceName).toEqual("test-resource");
254
- });
255
-
256
- it("should replace sequences of non-alphanumeric characters with a single -", () => {
257
- const resourceName = "test-*^%- -!=!resource";
258
- const sanitizedResourceName = sanitizeResourceName(resourceName);
259
- expect(sanitizedResourceName).toEqual("test-resource");
260
- });
261
-
262
- it("should truncate name to 250 characters", () => {
263
- const resourceName =
264
- "test-resourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresource";
265
- const sanitizedResourceName = sanitizeResourceName(resourceName);
266
- expect(sanitizedResourceName).toEqual(
267
- "test-resourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresourceresou",
268
- );
269
- });
270
-
271
- it("should remove leading and trailing non-letter characters", () => {
272
- const resourceName = " 1=-test-resource *2 ";
273
- const sanitizedResourceName = sanitizeResourceName(resourceName);
274
- expect(sanitizedResourceName).toEqual("test-resource");
275
- });
276
- });