pepr 0.33.0 → 0.34.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.
- package/README.md +2 -1
- package/dist/cli/banner.d.ts +2 -0
- package/dist/cli/banner.d.ts.map +1 -0
- package/dist/cli/build.d.ts +19 -0
- package/dist/cli/build.d.ts.map +1 -0
- package/dist/cli/deploy.d.ts +3 -0
- package/dist/cli/deploy.d.ts.map +1 -0
- package/dist/cli/dev.d.ts +3 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/format.d.ts +9 -0
- package/dist/cli/format.d.ts.map +1 -0
- package/dist/cli/init/index.d.ts +3 -0
- package/dist/cli/init/index.d.ts.map +1 -0
- package/dist/cli/init/templates.d.ts +196 -0
- package/dist/cli/init/templates.d.ts.map +1 -0
- package/dist/cli/init/utils.d.ts +21 -0
- package/dist/cli/init/utils.d.ts.map +1 -0
- package/dist/cli/init/utils.test.d.ts +2 -0
- package/dist/cli/init/utils.test.d.ts.map +1 -0
- package/dist/cli/init/walkthrough.d.ts +8 -0
- package/dist/cli/init/walkthrough.d.ts.map +1 -0
- package/dist/cli/init/walkthrough.test.d.ts +2 -0
- package/dist/cli/init/walkthrough.test.d.ts.map +1 -0
- package/dist/cli/kfc.d.ts +3 -0
- package/dist/cli/kfc.d.ts.map +1 -0
- package/dist/cli/monitor.d.ts +3 -0
- package/dist/cli/monitor.d.ts.map +1 -0
- package/dist/cli/root.d.ts +5 -0
- package/dist/cli/root.d.ts.map +1 -0
- package/dist/cli/update.d.ts +3 -0
- package/dist/cli/update.d.ts.map +1 -0
- package/dist/cli/uuid.d.ts +3 -0
- package/dist/cli/uuid.d.ts.map +1 -0
- package/dist/cli.js +69 -39
- package/dist/controller.js +1 -2
- package/dist/fixtures/loader.d.ts +5 -0
- package/dist/fixtures/loader.d.ts.map +1 -0
- package/dist/lib/assets/helm.d.ts +1 -0
- package/dist/lib/assets/helm.d.ts.map +1 -1
- package/dist/lib/assets/helm.test.d.ts +2 -0
- package/dist/lib/assets/helm.test.d.ts.map +1 -0
- package/dist/lib/assets/index.d.ts.map +1 -1
- package/dist/lib/assets/pods.d.ts +3 -0
- package/dist/lib/assets/pods.d.ts.map +1 -1
- package/dist/lib/assets/pods.test.d.ts +2 -0
- package/dist/lib/assets/pods.test.d.ts.map +1 -0
- package/dist/lib/assets/yaml.d.ts.map +1 -1
- package/dist/lib/controller/store.d.ts.map +1 -1
- package/dist/lib/errors.test.d.ts +2 -0
- package/dist/lib/errors.test.d.ts.map +1 -0
- package/dist/lib/filter.test.d.ts +2 -0
- package/dist/lib/filter.test.d.ts.map +1 -0
- package/dist/lib/helpers.d.ts +0 -5
- package/dist/lib/helpers.d.ts.map +1 -1
- package/dist/lib/helpers.test.d.ts +2 -0
- package/dist/lib/helpers.test.d.ts.map +1 -0
- package/dist/lib/included-files.test.d.ts +2 -0
- package/dist/lib/included-files.test.d.ts.map +1 -0
- package/dist/lib/logger.test.d.ts +2 -0
- package/dist/lib/logger.test.d.ts.map +1 -0
- package/dist/lib/metrics.d.ts +18 -0
- package/dist/lib/metrics.d.ts.map +1 -1
- package/dist/lib/metrics.test.d.ts +2 -0
- package/dist/lib/metrics.test.d.ts.map +1 -0
- package/dist/lib/module.test.d.ts +2 -0
- package/dist/lib/module.test.d.ts.map +1 -0
- package/dist/lib/mutate-request.test.d.ts +2 -0
- package/dist/lib/mutate-request.test.d.ts.map +1 -0
- package/dist/lib/queue.test.d.ts +2 -0
- package/dist/lib/queue.test.d.ts.map +1 -0
- package/dist/lib/schedule.test.d.ts +15 -0
- package/dist/lib/schedule.test.d.ts.map +1 -0
- package/dist/lib/storage.d.ts +1 -0
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.test.d.ts +2 -0
- package/dist/lib/storage.test.d.ts.map +1 -0
- package/dist/lib/tls.test.d.ts +2 -0
- package/dist/lib/tls.test.d.ts.map +1 -0
- package/dist/lib/utils.test.d.ts +2 -0
- package/dist/lib/utils.test.d.ts.map +1 -0
- package/dist/lib/validate-request.test.d.ts +2 -0
- package/dist/lib/validate-request.test.d.ts.map +1 -0
- package/dist/lib/watch-processor.d.ts.map +1 -1
- package/dist/lib/watch-processor.test.d.ts +2 -0
- package/dist/lib/watch-processor.test.d.ts.map +1 -0
- package/dist/lib.js +86 -24
- package/dist/lib.js.map +3 -3
- package/dist/sdk/sdk.test.d.ts +2 -0
- package/dist/sdk/sdk.test.d.ts.map +1 -0
- package/package.json +22 -16
- package/src/cli/banner.ts +63 -0
- package/src/cli/build.ts +370 -0
- package/src/cli/deploy.ts +105 -0
- package/src/cli/dev.ts +118 -0
- package/src/cli/format.ts +83 -0
- package/src/cli/init/index.ts +99 -0
- package/src/cli/init/templates.ts +124 -0
- package/src/cli/init/utils.test.ts +28 -0
- package/src/cli/init/utils.ts +55 -0
- package/src/cli/init/walkthrough.test.ts +21 -0
- package/src/cli/init/walkthrough.ts +96 -0
- package/src/cli/kfc.ts +45 -0
- package/src/cli/monitor.ts +101 -0
- package/src/cli/root.ts +12 -0
- package/src/cli/update.ts +95 -0
- package/src/cli/uuid.ts +44 -0
- package/src/fixtures/data/create-pod.json +271 -0
- package/src/fixtures/data/delete-pod.json +271 -0
- package/src/fixtures/loader.ts +18 -0
- package/src/lib/.prettierrc +14 -0
- package/src/lib/assets/helm.test.ts +64 -0
- package/src/lib/assets/helm.ts +35 -0
- package/src/lib/assets/index.ts +5 -1
- package/src/lib/assets/pods.test.ts +553 -0
- package/src/lib/assets/pods.ts +14 -6
- package/src/lib/assets/yaml.ts +15 -15
- package/src/lib/controller/index.ts +2 -2
- package/src/lib/controller/store.ts +0 -2
- package/src/lib/errors.test.ts +85 -0
- package/src/lib/filter.test.ts +384 -0
- package/src/lib/helpers.test.ts +1192 -0
- package/src/lib/helpers.ts +0 -17
- package/src/lib/included-files.test.ts +22 -0
- package/src/lib/logger.test.ts +18 -0
- package/src/lib/metrics.test.ts +132 -0
- package/src/lib/metrics.ts +68 -6
- package/src/lib/module.test.ts +126 -0
- package/src/lib/mutate-request.test.ts +188 -0
- package/src/lib/queue.test.ts +58 -0
- package/src/lib/schedule.test.ts +217 -0
- package/src/lib/storage.test.ts +216 -0
- package/src/lib/storage.ts +12 -4
- package/src/lib/tls.test.ts +18 -0
- package/src/lib/utils.test.ts +69 -0
- package/src/lib/validate-request.test.ts +124 -0
- package/src/lib/watch-processor.test.ts +322 -0
- package/src/lib/watch-processor.ts +20 -4
- package/src/sdk/sdk.test.ts +243 -0
- package/src/templates/.eslintrc.json +6 -0
- package/src/templates/capabilities/hello-pepr.ts +26 -11
- package/.prettierignore +0 -1
- package/CODE_OF_CONDUCT.md +0 -133
- package/SECURITY.md +0 -18
- package/SUPPORT.md +0 -16
- package/codecov.yaml +0 -19
- package/commitlint.config.js +0 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, jest, test } from "@jest/globals";
|
|
2
|
+
import { KubernetesObject } from "@kubernetes/client-node";
|
|
3
|
+
import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types";
|
|
4
|
+
import { Queue } from "./queue";
|
|
5
|
+
|
|
6
|
+
describe("Queue", () => {
|
|
7
|
+
let queue: Queue<KubernetesObject>;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
queue = new Queue();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("enqueue should add a pod to the queue and return a promise", async () => {
|
|
14
|
+
const pod = {
|
|
15
|
+
metadata: { name: "test-pod", namespace: "test-pod" },
|
|
16
|
+
};
|
|
17
|
+
const promise = queue.enqueue(pod, WatchPhase.Added);
|
|
18
|
+
expect(promise).toBeInstanceOf(Promise);
|
|
19
|
+
await promise;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("dequeue should process pods in FIFO order", async () => {
|
|
23
|
+
const mockPod = {
|
|
24
|
+
metadata: { name: "test-pod", namespace: "test-namespace" },
|
|
25
|
+
};
|
|
26
|
+
const mockPod2 = {
|
|
27
|
+
metadata: { name: "test-pod-2", namespace: "test-namespace-2" },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Enqueue two packages
|
|
31
|
+
const promise1 = queue.enqueue(mockPod, WatchPhase.Added);
|
|
32
|
+
const promise2 = queue.enqueue(mockPod2, WatchPhase.Modified);
|
|
33
|
+
|
|
34
|
+
// Wait for both promises to resolve
|
|
35
|
+
await promise1;
|
|
36
|
+
await promise2;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("dequeue should handle errors in pod processing", async () => {
|
|
40
|
+
const mockPod = {
|
|
41
|
+
metadata: { name: "test-pod", namespace: "test-namespace" },
|
|
42
|
+
};
|
|
43
|
+
const error = new Error("reconciliation failed");
|
|
44
|
+
jest.spyOn(queue, "setReconcile").mockRejectedValueOnce(error as never);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
await queue.enqueue(mockPod, WatchPhase.Added);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
expect(e).toBe(error);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Ensure that the queue is ready to process the next pod
|
|
53
|
+
const mockPod2 = {
|
|
54
|
+
metadata: { name: "test-pod-2", namespace: "test-namespace-2" },
|
|
55
|
+
};
|
|
56
|
+
await queue.enqueue(mockPod2, WatchPhase.Modified);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { beforeEach, describe, expect, it, jest, afterEach } from "@jest/globals";
|
|
5
|
+
import { OnSchedule, Schedule } from "./schedule";
|
|
6
|
+
import { Unsubscribe } from "./storage";
|
|
7
|
+
|
|
8
|
+
export class MockStorage {
|
|
9
|
+
private storage: Record<string, string> = {};
|
|
10
|
+
subscription: string;
|
|
11
|
+
constructor() {
|
|
12
|
+
this.subscription = "";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getItem(key: string): string | null {
|
|
16
|
+
return this.storage[key] || null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
setItem(key: string, value: string): void {
|
|
20
|
+
this.storage[key] = value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
setItemAndWait(key: string, value: string): Promise<void> {
|
|
24
|
+
return new Promise(resolve => {
|
|
25
|
+
this.storage[key] = value;
|
|
26
|
+
resolve();
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
removeItem(key: string): void {
|
|
31
|
+
delete this.storage[key];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
removeItemAndWait(key: string): Promise<void> {
|
|
35
|
+
return new Promise(resolve => {
|
|
36
|
+
delete this.storage[key];
|
|
37
|
+
resolve();
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
clear(): void {
|
|
42
|
+
this.storage = {};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
subscribe(): Unsubscribe {
|
|
46
|
+
// Expected 'this' to be used by class method 'subscribe'
|
|
47
|
+
this.subscription = "";
|
|
48
|
+
return true as unknown as Unsubscribe;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
onReady(): void {
|
|
52
|
+
// Expected 'this' to be used by class method 'onReady'
|
|
53
|
+
this.subscription = "";
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe("OnSchedule", () => {
|
|
59
|
+
const mockSchedule: Schedule = {
|
|
60
|
+
name: "test",
|
|
61
|
+
every: 1,
|
|
62
|
+
unit: "minutes",
|
|
63
|
+
run: jest.fn(),
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
jest.clearAllMocks();
|
|
68
|
+
jest.useRealTimers();
|
|
69
|
+
jest.clearAllTimers();
|
|
70
|
+
jest.resetModules();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
jest.useFakeTimers();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should create an instance of OnSchedule", () => {
|
|
78
|
+
const onSchedule = new OnSchedule(mockSchedule);
|
|
79
|
+
onSchedule.setStore(new MockStorage());
|
|
80
|
+
expect(onSchedule).toBeInstanceOf(OnSchedule);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should startInterval, run, and start", () => {
|
|
84
|
+
const onSchedule = new OnSchedule(mockSchedule);
|
|
85
|
+
onSchedule.setStore(new MockStorage());
|
|
86
|
+
onSchedule.completions = 0;
|
|
87
|
+
|
|
88
|
+
onSchedule.start();
|
|
89
|
+
|
|
90
|
+
onSchedule.startTime = new Date(new Date().getTime() + 100000);
|
|
91
|
+
|
|
92
|
+
onSchedule.setupInterval();
|
|
93
|
+
jest.advanceTimersByTime(100000);
|
|
94
|
+
|
|
95
|
+
const secondSchedule = new OnSchedule(mockSchedule);
|
|
96
|
+
onSchedule.setStore(new MockStorage());
|
|
97
|
+
secondSchedule.completions = 9;
|
|
98
|
+
secondSchedule.duration = 1;
|
|
99
|
+
secondSchedule.start();
|
|
100
|
+
jest.advanceTimersByTime(100000);
|
|
101
|
+
expect(secondSchedule.completions).toBe(0);
|
|
102
|
+
jest.runOnlyPendingTimers();
|
|
103
|
+
jest.clearAllTimers();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("should stop, removeItem, and removeItem", () => {
|
|
107
|
+
const onSchedule = new OnSchedule(mockSchedule);
|
|
108
|
+
onSchedule.setStore(new MockStorage());
|
|
109
|
+
const removeItemSpy = jest.spyOn(onSchedule.store as MockStorage, "removeItem");
|
|
110
|
+
|
|
111
|
+
onSchedule.startInterval();
|
|
112
|
+
onSchedule.stop();
|
|
113
|
+
|
|
114
|
+
expect(onSchedule.intervalId).toBeNull();
|
|
115
|
+
expect(removeItemSpy).toHaveBeenCalled();
|
|
116
|
+
|
|
117
|
+
onSchedule.intervalId = 9 as unknown as NodeJS.Timeout;
|
|
118
|
+
onSchedule.stop();
|
|
119
|
+
expect(onSchedule.intervalId).toBeNull();
|
|
120
|
+
jest.runOnlyPendingTimers();
|
|
121
|
+
jest.clearAllTimers();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should getDuration", () => {
|
|
125
|
+
// test second
|
|
126
|
+
mockSchedule.every = 10;
|
|
127
|
+
mockSchedule.unit = "seconds";
|
|
128
|
+
const onSchedule = new OnSchedule(mockSchedule);
|
|
129
|
+
onSchedule.getDuration();
|
|
130
|
+
expect(onSchedule.duration).toBe(10000);
|
|
131
|
+
|
|
132
|
+
// test second error
|
|
133
|
+
mockSchedule.every = 8;
|
|
134
|
+
try {
|
|
135
|
+
onSchedule.getDuration();
|
|
136
|
+
} catch (e) {
|
|
137
|
+
expect(e).toEqual(new Error("10 Seconds in the smallest interval allowed"));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// test minute(s)
|
|
141
|
+
onSchedule.unit = "minutes";
|
|
142
|
+
onSchedule.getDuration();
|
|
143
|
+
expect(onSchedule.duration).toBe(600000);
|
|
144
|
+
onSchedule.unit = "minute";
|
|
145
|
+
onSchedule.getDuration();
|
|
146
|
+
expect(onSchedule.duration).toBe(600000);
|
|
147
|
+
|
|
148
|
+
// test hour(s)
|
|
149
|
+
onSchedule.unit = "hours";
|
|
150
|
+
onSchedule.getDuration();
|
|
151
|
+
expect(onSchedule.duration).toBe(36000000);
|
|
152
|
+
onSchedule.unit = "hour";
|
|
153
|
+
onSchedule.getDuration();
|
|
154
|
+
expect(onSchedule.duration).toBe(36000000);
|
|
155
|
+
|
|
156
|
+
// test invalid unit
|
|
157
|
+
onSchedule.unit = "second";
|
|
158
|
+
try {
|
|
159
|
+
onSchedule.getDuration();
|
|
160
|
+
} catch (e) {
|
|
161
|
+
expect(e).toEqual(new Error("Invalid time unit"));
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("should setupInterval", () => {
|
|
166
|
+
// startTime and lastTimestamp should set startTime to undefined
|
|
167
|
+
mockSchedule.startTime = new Date();
|
|
168
|
+
mockSchedule.unit = "seconds";
|
|
169
|
+
mockSchedule.every = 10;
|
|
170
|
+
const onSchedule = new OnSchedule(mockSchedule);
|
|
171
|
+
onSchedule.setStore(new MockStorage());
|
|
172
|
+
onSchedule.lastTimestamp = new Date();
|
|
173
|
+
onSchedule.setupInterval();
|
|
174
|
+
expect(onSchedule.startTime).toBeUndefined();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("should call setItem during saveToStore", () => {
|
|
178
|
+
const onSchedule = new OnSchedule(mockSchedule);
|
|
179
|
+
onSchedule.setStore(new MockStorage());
|
|
180
|
+
|
|
181
|
+
const setItemSpy = jest.spyOn(onSchedule.store as MockStorage, "setItem");
|
|
182
|
+
onSchedule.saveToStore();
|
|
183
|
+
expect(setItemSpy).toHaveBeenCalledTimes(1);
|
|
184
|
+
});
|
|
185
|
+
it("checkStore retrieves values from the store", () => {
|
|
186
|
+
mockSchedule.run = () => {};
|
|
187
|
+
const onSchedule = new OnSchedule(mockSchedule);
|
|
188
|
+
onSchedule.setStore(new MockStorage());
|
|
189
|
+
const getItemSpy = jest.spyOn(onSchedule.store as MockStorage, "getItem");
|
|
190
|
+
const startTime = "doesn't";
|
|
191
|
+
const lastTimestamp = "matter";
|
|
192
|
+
getItemSpy.mockReturnValue(
|
|
193
|
+
JSON.stringify({
|
|
194
|
+
completions: 1,
|
|
195
|
+
startTime,
|
|
196
|
+
lastTimestamp,
|
|
197
|
+
}),
|
|
198
|
+
);
|
|
199
|
+
onSchedule.checkStore();
|
|
200
|
+
|
|
201
|
+
expect(getItemSpy).toHaveBeenCalledTimes(1);
|
|
202
|
+
|
|
203
|
+
// // Verify that the values were correctly retrieved and assigned
|
|
204
|
+
expect(onSchedule.completions).toBe(1);
|
|
205
|
+
expect(onSchedule.startTime).toEqual(startTime);
|
|
206
|
+
expect(onSchedule.lastTimestamp).toEqual(lastTimestamp);
|
|
207
|
+
|
|
208
|
+
getItemSpy.mockReturnValue(
|
|
209
|
+
JSON.stringify({
|
|
210
|
+
lastTimestamp: undefined,
|
|
211
|
+
}),
|
|
212
|
+
);
|
|
213
|
+
onSchedule.checkStore();
|
|
214
|
+
|
|
215
|
+
expect(onSchedule.lastTimestamp).toEqual(undefined);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
|
|
5
|
+
import { DataStore, Storage, v2StoreKey, v2UnescapedStoreKey, stripV2Prefix } from "./storage";
|
|
6
|
+
import fc from "fast-check";
|
|
7
|
+
|
|
8
|
+
describe("stripV2Prefix", () => {
|
|
9
|
+
it("should remove the v2 prefix", () => {
|
|
10
|
+
const keys = ["v2-key1", "v2-key2", "v2-key3", "v2-key4", "v2-key5"];
|
|
11
|
+
const results = ["key1", "key2", "key3", "key4", "key5"];
|
|
12
|
+
|
|
13
|
+
for (let i = 0; i < keys.length; i++) {
|
|
14
|
+
const result = stripV2Prefix(keys[i]);
|
|
15
|
+
expect(result).toEqual(results[i]);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
describe("v2StoreKey", () => {
|
|
20
|
+
it("should prefix the key with v2", () => {
|
|
21
|
+
const keys = ["key1", "key2", "key3", "key4", "key5"];
|
|
22
|
+
const results = ["v2-key1", "v2-key2", "v2-key3", "v2-key4", "v2-key5"];
|
|
23
|
+
|
|
24
|
+
for (let i = 0; i < keys.length; i++) {
|
|
25
|
+
const result = v2StoreKey(keys[i]);
|
|
26
|
+
expect(result).toEqual(results[i]);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("v2UnescapedStoreKey", () => {
|
|
32
|
+
it("should prefix the key with v2", () => {
|
|
33
|
+
const keys = ["https://google.com", "sso-client-http://bin", "key3", "key4", "key5"];
|
|
34
|
+
const results = ["v2-https://google.com", "v2-sso-client-http://bin", "v2-key3", "v2-key4", "v2-key5"];
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < keys.length; i++) {
|
|
37
|
+
const result = v2UnescapedStoreKey(keys[i]);
|
|
38
|
+
expect(result).toEqual(results[i]);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe("Storage with fuzzing and property-based tests", () => {
|
|
43
|
+
let storage: Storage;
|
|
44
|
+
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
storage = new Storage();
|
|
47
|
+
storage.registerSender(jest.fn());
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should correctly set and retrieve items", () => {
|
|
51
|
+
fc.assert(
|
|
52
|
+
fc.property(fc.string(), fc.string(), (key, value) => {
|
|
53
|
+
storage.setItem(key, value);
|
|
54
|
+
const mockData: DataStore = { [v2UnescapedStoreKey(key)]: value };
|
|
55
|
+
storage.receive(mockData);
|
|
56
|
+
if (value === "") {
|
|
57
|
+
expect(storage.getItem(key)).toBeNull();
|
|
58
|
+
} else {
|
|
59
|
+
expect(storage.getItem(key)).toEqual(value);
|
|
60
|
+
}
|
|
61
|
+
}),
|
|
62
|
+
{ numRuns: 100 },
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should return null for non-existing items", () => {
|
|
67
|
+
fc.assert(
|
|
68
|
+
fc.property(fc.string(), key => {
|
|
69
|
+
expect(storage.getItem(key)).toBeNull();
|
|
70
|
+
}),
|
|
71
|
+
{ numRuns: 100 },
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should correctly remove items", () => {
|
|
76
|
+
fc.assert(
|
|
77
|
+
fc.property(fc.string(), fc.string(), (key, value) => {
|
|
78
|
+
storage.setItem(key, value);
|
|
79
|
+
storage.removeItem(key);
|
|
80
|
+
expect(storage.getItem(key)).toBeNull();
|
|
81
|
+
}),
|
|
82
|
+
{ numRuns: 100 },
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should ensure all set items are v2-coded internally", () => {
|
|
87
|
+
fc.assert(
|
|
88
|
+
fc.property(fc.string(), fc.string(), (key, value) => {
|
|
89
|
+
storage.setItem(key, value);
|
|
90
|
+
const mockData: DataStore = { [v2UnescapedStoreKey(key)]: value };
|
|
91
|
+
storage.receive(mockData);
|
|
92
|
+
if (value === "") {
|
|
93
|
+
expect(storage.getItem(key)).toBeNull();
|
|
94
|
+
} else {
|
|
95
|
+
expect(storage.getItem(key)).toEqual(value);
|
|
96
|
+
}
|
|
97
|
+
}),
|
|
98
|
+
{ numRuns: 100 },
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
describe("Storage", () => {
|
|
103
|
+
let storage: Storage;
|
|
104
|
+
|
|
105
|
+
beforeEach(() => {
|
|
106
|
+
storage = new Storage();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should set an item", () => {
|
|
110
|
+
const mockSender = jest.fn();
|
|
111
|
+
storage.registerSender(mockSender);
|
|
112
|
+
const key = "key1";
|
|
113
|
+
storage.setItem(key, "value1");
|
|
114
|
+
|
|
115
|
+
expect(mockSender).toHaveBeenCalledWith("add", [v2UnescapedStoreKey(key)], "value1");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should set an item and wait", () => {
|
|
119
|
+
const mockSender = jest.fn();
|
|
120
|
+
storage.registerSender(mockSender);
|
|
121
|
+
jest.useFakeTimers();
|
|
122
|
+
const keys = ["key1", "https://google.com", "sso-client-http://bin"];
|
|
123
|
+
|
|
124
|
+
keys.map(key => {
|
|
125
|
+
void storage.setItemAndWait(key, "value1");
|
|
126
|
+
expect(mockSender).toHaveBeenCalledWith("add", [v2StoreKey(key)], "value1");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
jest.useRealTimers();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("should remove an item and wait", () => {
|
|
133
|
+
const mockSender = jest.fn();
|
|
134
|
+
storage.registerSender(mockSender);
|
|
135
|
+
jest.useFakeTimers();
|
|
136
|
+
|
|
137
|
+
const key = "key1";
|
|
138
|
+
// asserting on sender invocation rather than Promise so no need to wait
|
|
139
|
+
void storage.removeItemAndWait(key);
|
|
140
|
+
|
|
141
|
+
expect(mockSender).toHaveBeenCalledWith("remove", [v2StoreKey(key)], undefined);
|
|
142
|
+
jest.useRealTimers();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should remove an item", () => {
|
|
146
|
+
const mockSender = jest.fn();
|
|
147
|
+
storage.registerSender(mockSender);
|
|
148
|
+
const key = "key1";
|
|
149
|
+
storage.removeItem(key);
|
|
150
|
+
|
|
151
|
+
expect(mockSender).toHaveBeenCalledWith("remove", [v2StoreKey(key)], undefined);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("should clear all items", () => {
|
|
155
|
+
const mockSender = jest.fn();
|
|
156
|
+
storage.registerSender(mockSender);
|
|
157
|
+
|
|
158
|
+
storage.receive({ key1: "value1", key2: "value2", "sso-client-http://bin": "value3" });
|
|
159
|
+
storage.clear();
|
|
160
|
+
|
|
161
|
+
expect(mockSender).toHaveBeenCalledWith("remove", ["key1", "key2", "sso-client-http:~1~1bin"], undefined);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("should get an item", () => {
|
|
165
|
+
const keys = ["key1", "!", "!", "pepr", "https://google.com", "sftp://here:22", "!"];
|
|
166
|
+
const results = ["value1", null, "!", "was-here", "3f7dd007-568f-4f4a-bbac-2e6bfff93860", "your-machine", " "];
|
|
167
|
+
|
|
168
|
+
keys.map((key, i) => {
|
|
169
|
+
const mockData: DataStore = { [v2UnescapedStoreKey(key)]: results[i]! };
|
|
170
|
+
|
|
171
|
+
storage.receive(mockData);
|
|
172
|
+
const value = storage.getItem(keys[i]);
|
|
173
|
+
expect(value).toEqual(results[i]);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("should return null for non-existing item", () => {
|
|
178
|
+
const value = storage.getItem("key1");
|
|
179
|
+
|
|
180
|
+
expect(value).toBeNull();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should subscribe and unsubscribe", () => {
|
|
184
|
+
const mockSubscriber = jest.fn();
|
|
185
|
+
const unsubscribe = storage.subscribe(mockSubscriber);
|
|
186
|
+
|
|
187
|
+
storage.receive({ key1: "value1" });
|
|
188
|
+
|
|
189
|
+
expect(mockSubscriber).toHaveBeenCalledWith({ key1: "value1" });
|
|
190
|
+
|
|
191
|
+
unsubscribe();
|
|
192
|
+
|
|
193
|
+
storage.receive({ key2: "value2" });
|
|
194
|
+
|
|
195
|
+
// Should not be called again after unsubscribe
|
|
196
|
+
expect(mockSubscriber).toHaveBeenCalledTimes(1);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("should call onReady handlers", () => {
|
|
200
|
+
const mockReadyHandler = jest.fn();
|
|
201
|
+
|
|
202
|
+
storage.onReady(mockReadyHandler);
|
|
203
|
+
storage.receive({ key1: "value1" });
|
|
204
|
+
|
|
205
|
+
expect(mockReadyHandler).toHaveBeenCalledWith({ key1: "value1" });
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("should handle null data in receive method", () => {
|
|
209
|
+
const mockSubscriber = jest.fn();
|
|
210
|
+
storage.subscribe(mockSubscriber);
|
|
211
|
+
|
|
212
|
+
storage.receive(null as unknown as DataStore);
|
|
213
|
+
|
|
214
|
+
expect(mockSubscriber).toHaveBeenCalledWith({});
|
|
215
|
+
});
|
|
216
|
+
});
|
package/src/lib/storage.ts
CHANGED
|
@@ -17,6 +17,10 @@ export function v2StoreKey(key: string) {
|
|
|
17
17
|
return `${STORE_VERSION_PREFIX}-${pointer.escape(key)}`;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export function v2UnescapedStoreKey(key: string) {
|
|
21
|
+
return `${STORE_VERSION_PREFIX}-${key}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
export function stripV2Prefix(key: string) {
|
|
21
25
|
return key.replace(/^v2-/, "");
|
|
22
26
|
}
|
|
@@ -95,7 +99,7 @@ export class Storage implements PeprStore {
|
|
|
95
99
|
};
|
|
96
100
|
|
|
97
101
|
getItem = (key: string) => {
|
|
98
|
-
const result = this.#store[
|
|
102
|
+
const result = this.#store[v2UnescapedStoreKey(key)] || null;
|
|
99
103
|
if (result !== null && typeof result !== "function" && typeof result !== "object") {
|
|
100
104
|
return result;
|
|
101
105
|
}
|
|
@@ -103,7 +107,10 @@ export class Storage implements PeprStore {
|
|
|
103
107
|
};
|
|
104
108
|
|
|
105
109
|
clear = () => {
|
|
106
|
-
this.#dispatchUpdate(
|
|
110
|
+
this.#dispatchUpdate(
|
|
111
|
+
"remove",
|
|
112
|
+
Object.keys(this.#store).map(key => pointer.escape(key)),
|
|
113
|
+
);
|
|
107
114
|
};
|
|
108
115
|
|
|
109
116
|
removeItem = (key: string) => {
|
|
@@ -124,9 +131,10 @@ export class Storage implements PeprStore {
|
|
|
124
131
|
*/
|
|
125
132
|
setItemAndWait = (key: string, value: string) => {
|
|
126
133
|
this.#dispatchUpdate("add", [v2StoreKey(key)], value);
|
|
134
|
+
|
|
127
135
|
return new Promise<void>((resolve, reject) => {
|
|
128
136
|
const unsubscribe = this.subscribe(data => {
|
|
129
|
-
if (data[`${
|
|
137
|
+
if (data[`${v2UnescapedStoreKey(key)}`] === value) {
|
|
130
138
|
unsubscribe();
|
|
131
139
|
resolve();
|
|
132
140
|
}
|
|
@@ -151,7 +159,7 @@ export class Storage implements PeprStore {
|
|
|
151
159
|
this.#dispatchUpdate("remove", [v2StoreKey(key)]);
|
|
152
160
|
return new Promise<void>((resolve, reject) => {
|
|
153
161
|
const unsubscribe = this.subscribe(data => {
|
|
154
|
-
if (!Object.hasOwn(data, `${
|
|
162
|
+
if (!Object.hasOwn(data, `${v2UnescapedStoreKey(key)}`)) {
|
|
155
163
|
unsubscribe();
|
|
156
164
|
resolve();
|
|
157
165
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { expect, test, describe } from "@jest/globals";
|
|
5
|
+
import { genTLS } from "./tls";
|
|
6
|
+
|
|
7
|
+
describe("tls", () => {
|
|
8
|
+
test("genTLS should generate a valid TLSOut object", () => {
|
|
9
|
+
const tls = genTLS("test");
|
|
10
|
+
expect(tls).toHaveProperty("ca");
|
|
11
|
+
expect(tls).toHaveProperty("crt");
|
|
12
|
+
expect(tls).toHaveProperty("key");
|
|
13
|
+
expect(tls).toHaveProperty("pem");
|
|
14
|
+
expect(tls.pem).toHaveProperty("ca");
|
|
15
|
+
expect(tls.pem).toHaveProperty("crt");
|
|
16
|
+
expect(tls.pem).toHaveProperty("key");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { expect, test, describe } from "@jest/globals";
|
|
5
|
+
import { convertToBase64Map, convertFromBase64Map, base64Decode, base64Encode } from "./utils";
|
|
6
|
+
|
|
7
|
+
describe("utils", () => {
|
|
8
|
+
test("convertToBase64Map should encode all ascii values and skip listed values in skip array", () => {
|
|
9
|
+
const obj = {
|
|
10
|
+
data: {
|
|
11
|
+
test1: "test1",
|
|
12
|
+
test2: "test2",
|
|
13
|
+
test3: "test3",
|
|
14
|
+
test4: "test4",
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
const skip = ["test2", "test4"];
|
|
18
|
+
convertToBase64Map(obj, skip);
|
|
19
|
+
expect(obj.data["test1"]).toBe(base64Encode("test1"));
|
|
20
|
+
expect(obj.data["test2"]).toBe("test2");
|
|
21
|
+
expect(obj.data["test3"]).toBe(base64Encode("test3"));
|
|
22
|
+
expect(obj.data["test4"]).toBe("test4");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("convertFromBase64Map should decode all ascii values and skip values in skip array", () => {
|
|
26
|
+
const obj = {
|
|
27
|
+
data: {
|
|
28
|
+
test1: base64Encode("test1"),
|
|
29
|
+
test2: "test2",
|
|
30
|
+
test3: base64Encode("test3"),
|
|
31
|
+
test4: "test4",
|
|
32
|
+
test5: undefined as unknown as string,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
const skip = convertFromBase64Map(obj);
|
|
36
|
+
expect(obj.data["test1"]).toBe("test1");
|
|
37
|
+
expect(obj.data["test2"]).toBe("test2");
|
|
38
|
+
expect(obj.data["test3"]).toBe("test3");
|
|
39
|
+
expect(obj.data["test4"]).toBe("test4");
|
|
40
|
+
expect(skip).toEqual(["test2", "test4"]);
|
|
41
|
+
expect(obj.data["test5"]).toBe("");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("base64Decode should decode a base64 string", () => {
|
|
45
|
+
const data = "dGVzdDE=";
|
|
46
|
+
expect(base64Decode(data)).toBe("test1");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("base64Encode should encode a string to base64", () => {
|
|
50
|
+
const data = "test1";
|
|
51
|
+
expect(base64Encode(data)).toBe("dGVzdDE=");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("convertToBase64Map empty object", () => {
|
|
55
|
+
const obj = {};
|
|
56
|
+
const objOut = { data: {} };
|
|
57
|
+
convertToBase64Map(obj, []);
|
|
58
|
+
expect(obj).toStrictEqual(objOut);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("convertFromBase64Map empty object", () => {
|
|
62
|
+
const obj = {};
|
|
63
|
+
const objOut = { data: {} };
|
|
64
|
+
const skip = convertFromBase64Map(obj);
|
|
65
|
+
expect(obj).toStrictEqual(objOut);
|
|
66
|
+
expect(skip).toEqual([]);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
// Path: src/lib/utils.ts
|