fenneckit 1.2.0 β 1.2.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 +58 -517
- package/docs/api-reference.md +72 -0
- package/docs/audit-log.md +47 -0
- package/docs/best-practices.md +23 -0
- package/docs/getting-started.md +82 -0
- package/docs/http-kit.md +71 -0
- package/docs/storage.md +90 -0
- package/index.d.ts +5 -0
- package/index.d.ts.map +1 -0
- package/libs/fenneckit.d.ts +2 -0
- package/libs/fenneckit.d.ts.map +1 -0
- package/libs/labs.d.ts +70 -0
- package/libs/labs.d.ts.map +1 -0
- package/libs/labs.js +4 -0
- package/libs/testing.d.ts +9 -0
- package/libs/testing.d.ts.map +1 -0
- package/package.json +11 -1
- package/types.d.ts +39 -0
- package/types.d.ts.map +1 -0
- package/types.js +1 -0
- package/utility/report.d.ts +2 -0
- package/utility/report.d.ts.map +1 -0
package/README.md
CHANGED
|
@@ -1,547 +1,88 @@
|
|
|
1
|
-
# π¦ FennecKit
|
|
1
|
+
# π¦ FennecKit
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
**English**: A Complete Storage + Security + API Testing System for Practical and Seamless Data Analysis
|
|
11
|
-
|
|
12
|
-
**Zero Config** β’ Sequential Labs β’ Inter-Lab Data Sharing (within the same file) β’ Store Management β’ Encrypted Secrets β’ Namespaces β’ Audit Log β’ HTTP Testing Kit
|
|
13
|
-
|
|
14
|
-
**π¦Example** : soon
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## π― What is FennecKit?
|
|
19
|
-
|
|
20
|
-
FennecKit is a lightweight, **zero-config** testing & development utility built around the concept of **Labs**.
|
|
21
|
-
|
|
22
|
-
### What is a "Lab"?
|
|
23
|
-
|
|
24
|
-
A **Lab** is a collection of sequential tasks (a workflow) that together accomplish one objective.
|
|
25
|
-
|
|
26
|
-
**Examples**:
|
|
27
|
-
- User Registration Lab β create user β validate β save to DB β send email
|
|
28
|
-
- Payment Processing Lab β validate β charge β generate invoice
|
|
29
|
-
- API Testing Lab β hit endpoints β verify responses β check side effects
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
// Lab = Collection of sequential tasks
|
|
33
|
-
await newLabs("User Registration Lab", async (kit) => {
|
|
34
|
-
await kit.test("Validate Email", async () => {
|
|
35
|
-
kit.done("Email validated");
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
await kit.test("Save to Database", async () => {
|
|
39
|
-
kit.done("User saved");
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
await kit.test("Send Verification Email", async () => {
|
|
43
|
-
kit.done("Email sent");
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
## β‘ Zero Config & Runner
|
|
51
|
-
|
|
52
|
-
FennecKit requires **no configuration files**.
|
|
53
|
-
|
|
54
|
-
### How to run
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
# Run a specific lab file
|
|
58
|
-
npx fenneckit lab.js
|
|
59
|
-
|
|
60
|
-
# Or just
|
|
61
|
-
npx fenneckit
|
|
62
|
-
|
|
63
|
-
# The runner automatically finds all *.labs.js / *.labs.ts files
|
|
64
|
-
# and executes them one after another (sequentially)
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
**What the runner does**:
|
|
68
|
-
1. Discovers lab files in the current directory (and subdirectories if configured)
|
|
69
|
-
2. Runs each file **one by one**
|
|
70
|
-
3. Inside each file, Labs run in the order they are written
|
|
71
|
-
4. Generates a report (`fenneckit.md`) after execution
|
|
72
|
-
|
|
73
|
-
> **Important limitation**
|
|
74
|
-
> Data sharing (`setStore` / `getStore` / secrets / namespaces) only works **inside the same file**.
|
|
75
|
-
> Different lab files **cannot** share STORE, TEMP, Secrets or Namespaces with each other.
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
|
|
79
|
-
## π³ Data Sharing Hierarchy (Tree Structure)
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
π FennecKit Execution
|
|
83
|
-
β
|
|
84
|
-
ββ π file1.labs.js (STORE Instance #1 + SecretVault + Namespaces)
|
|
85
|
-
β β
|
|
86
|
-
β ββ π¬ Lab 1 (User Registration)
|
|
87
|
-
β β ββ π Test 1: Create User
|
|
88
|
-
β β β ββ STORE: {"userId": "123"} β
Shared with Lab 2
|
|
89
|
-
β β β ββ SECRET: encrypted token β
Shared with Lab 2
|
|
90
|
-
β β β ββ NAMESPACE "user": {...} β
Shared with Lab 2
|
|
91
|
-
β β β ββ TEMP: {"token": "abc"} β Only here
|
|
92
|
-
β β β
|
|
93
|
-
β β ββ π Test 2: Send Email
|
|
94
|
-
β β ββ Can access STORE / SECRET / Namespaces from Test 1
|
|
95
|
-
β β
|
|
96
|
-
β ββ π¬ Lab 2 (Authentication)
|
|
97
|
-
β β ββ π Test 1: Generate Token
|
|
98
|
-
β β β ββ STORE: {"userId": "123"} β
From Lab 1
|
|
99
|
-
β β β ββ SECRET / Namespace β
From Lab 1
|
|
100
|
-
β β β ββ TEMP: {"token": "new"} β Only here
|
|
101
|
-
β β β
|
|
102
|
-
β β ββ π Test 2: Verify Token
|
|
103
|
-
β β ββ Can access STORE / SECRET / Namespaces from Labs 1 & 2
|
|
104
|
-
β β
|
|
105
|
-
β ββ π¬ Lab 3 (Cleanup)
|
|
106
|
-
β ββ File STORE + Secrets + Namespaces cleared when execution ends
|
|
107
|
-
β
|
|
108
|
-
ββ π file2.labs.js (Completely ISOLATED)
|
|
109
|
-
β ββ β CANNOT access anything from file1.labs.js
|
|
110
|
-
β
|
|
111
|
-
ββ π file3.labs.js (Completely ISOLATED)
|
|
112
|
-
ββ β Isolated from all other files
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### Understanding the Hierarchy
|
|
116
|
-
|
|
117
|
-
**π΄ Level 1: Different Files = NO Data Sharing**
|
|
118
|
-
Each file gets its own isolated STORE, SecretVault and Namespaces.
|
|
119
|
-
|
|
120
|
-
**π‘ Level 2: Same File, Different Labs = STORE + Secrets + Namespaces Sharing**
|
|
121
|
-
Data set in Lab 1 is available in Lab 2, Lab 3, etc. (until cleared).
|
|
122
|
-
|
|
123
|
-
**π’ Level 3: Same Lab, Different Tests = STORE + TEMP + Secrets + Namespaces Sharing**
|
|
124
|
-
TEMP is automatically cleared when the Lab ends. Everything else remains.
|
|
125
|
-
|
|
126
|
-
---
|
|
127
|
-
|
|
128
|
-
## π Data Communication Between Labs (Inter-Lab Communication)
|
|
129
|
-
|
|
130
|
-
### The Problem with Jest / Vitest
|
|
131
|
-
|
|
132
|
-
In Jest and Vitest every test is isolated. You cannot pass data from one test to another.
|
|
133
|
-
|
|
134
|
-
### FennecKit Solution β Storage Levels (v1.2.0)
|
|
135
|
-
|
|
136
|
-
```
|
|
137
|
-
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
138
|
-
β FennecKit Storage System (Per File) β
|
|
139
|
-
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
|
|
140
|
-
β β
|
|
141
|
-
β LEVEL 1: FILE SCOPE β
|
|
142
|
-
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
|
|
143
|
-
β β STORE (Global - Persistent within file) β β
|
|
144
|
-
β β ββ Shared across ALL Labs in this file β β
|
|
145
|
-
β β ββ Available until file execution ends β β
|
|
146
|
-
β β ββ Can be manually cleared with clearStore() β β
|
|
147
|
-
β β ββ Example: userId, orderData, status β β
|
|
148
|
-
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
|
|
149
|
-
β β
|
|
150
|
-
β LEVEL 2: ENCRYPTED SECRETS (AES-256-GCM) β
|
|
151
|
-
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
|
|
152
|
-
β β SECRET VAULT β β
|
|
153
|
-
β β ββ Encrypted at rest inside the process β β
|
|
154
|
-
β β ββ Shared across Labs in the same file β β
|
|
155
|
-
β β ββ setSecret / getSecret / clearSecret β β
|
|
156
|
-
β β ββ Example: auth tokens, API keys, passwords β β
|
|
157
|
-
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
|
|
158
|
-
β β
|
|
159
|
-
β LEVEL 3: NAMESPACES β
|
|
160
|
-
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
|
|
161
|
-
β β kit.namespace("user") / kit.namespace("order") β β
|
|
162
|
-
β β ββ Isolated key-value stores β β
|
|
163
|
-
β β ββ Shared across Labs in the same file β β
|
|
164
|
-
β β ββ Perfect for grouping related data β β
|
|
165
|
-
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
|
|
166
|
-
β β
|
|
167
|
-
β LEVEL 4: LAB-LOCAL SCOPE β
|
|
168
|
-
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
|
|
169
|
-
β β TEMP (Local - Auto-Cleaned) β β
|
|
170
|
-
β β ββ Only available inside current Lab β β
|
|
171
|
-
β β ββ Automatically cleared when Lab ends β β
|
|
172
|
-
β β ββ Example: timestamps, temp calculations β β
|
|
173
|
-
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
|
|
174
|
-
β β
|
|
175
|
-
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
---
|
|
179
|
-
|
|
180
|
-
## π NEW in v1.2.0: Secret Vault (AES-256-GCM)
|
|
181
|
-
|
|
182
|
-
Sensitive data should never live in plain STORE.
|
|
183
|
-
|
|
184
|
-
```typescript
|
|
185
|
-
await newLabs("Auth Lab", async (kit) => {
|
|
186
|
-
await kit.test("Login", async () => {
|
|
187
|
-
// Encrypted storage
|
|
188
|
-
kit.setSecret("accessToken", "eyJhbGciOiJIUzI1NiIs...");
|
|
189
|
-
kit.setSecret("refreshToken", "rt_abc123");
|
|
190
|
-
|
|
191
|
-
kit.done("Tokens stored securely");
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
await kit.test("Use Token", async () => {
|
|
195
|
-
const token = kit.getSecret("accessToken"); // decrypted on the fly
|
|
196
|
-
kit.http.setAuth("bearer", token!);
|
|
197
|
-
kit.done("Token retrieved");
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
await kit.test("Cleanup", async () => {
|
|
201
|
-
kit.clearSecret("accessToken"); // remove one key
|
|
202
|
-
// kit.clearSecret(); // clear entire vault
|
|
203
|
-
kit.done("Secrets cleared");
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
**TTL Support for Secrets**
|
|
209
|
-
```typescript
|
|
210
|
-
kit.setSecretWithTTL("tempToken", "abc123", 30_000); // auto-delete after 30s
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
---
|
|
214
|
-
|
|
215
|
-
## ποΈ NEW in v1.2.0: Namespaced Store
|
|
216
|
-
|
|
217
|
-
Organize data into logical groups.
|
|
218
|
-
|
|
219
|
-
```typescript
|
|
220
|
-
await newLabs("E-Commerce Flow", async (kit) => {
|
|
221
|
-
const user = kit.namespace("user");
|
|
222
|
-
const order = kit.namespace("order");
|
|
223
|
-
const cart = kit.namespace("cart");
|
|
224
|
-
|
|
225
|
-
await kit.test("Register", async () => {
|
|
226
|
-
user.set("id", "usr_123");
|
|
227
|
-
user.set("email", "john@example.com");
|
|
228
|
-
kit.done("User namespace populated");
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
await kit.test("Create Order", async () => {
|
|
232
|
-
order.set("id", "ord_456");
|
|
233
|
-
order.set("total", 299.99);
|
|
234
|
-
kit.done("Order namespace populated");
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
await kit.test("Read Later", async () => {
|
|
238
|
-
const userId = kit.getNamespace("user")?.get("id"); // "usr_123"
|
|
239
|
-
const total = order.get("total"); // 299.99
|
|
240
|
-
kit.done(`User ${userId} ordered ${total}`);
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
```
|
|
5
|
+
<div align="center">
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/fenneckit)
|
|
8
|
+
[](https://www.npmjs.com/package/fenneckit)
|
|
244
9
|
|
|
245
|
-
|
|
10
|
+
[](https://nodejs.org/)
|
|
11
|
+
[](https://www.typescriptlang.org/)
|
|
12
|
+
[](LICENSE)
|
|
246
13
|
|
|
247
|
-
|
|
14
|
+
</div>
|
|
15
|
+
**Lab-based testing utility.** Sequential tests that share data, zero config, encrypted secrets, and a built-in HTTP client.
|
|
248
16
|
|
|
249
|
-
|
|
17
|
+
## Why FennecKit?
|
|
250
18
|
|
|
251
|
-
|
|
252
|
-
await newLabs("API Smoke Test", async (kit) => {
|
|
253
|
-
// Set auth once
|
|
254
|
-
kit.http.setAuth("bearer", "your-token");
|
|
255
|
-
// or kit.http.setAuth("basic", "base64creds");
|
|
256
|
-
// or kit.http.setAuth("api-key", "key123");
|
|
19
|
+
In Jest/Vitest every test starts fresh, so multi-step workflows (create user β read user β delete user) need mocks or duplicated setup. In FennecKit, tests inside one **Lab** run in order and share data.
|
|
257
20
|
|
|
258
|
-
|
|
259
|
-
const res = await kit.http.get("https://api.example.com/users", {
|
|
260
|
-
timeout: 5000,
|
|
261
|
-
retry: { max: 3, delay: 1000 }
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
if (res.status !== 200) {
|
|
265
|
-
kit.err(`Expected 200, got ${res.status}`);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
kit.setStore("users", res.data);
|
|
269
|
-
kit.done(`Fetched ${res.data.length} users`);
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
await kit.test("POST Order", async () => {
|
|
273
|
-
const res = await kit.http.post("https://api.example.com/orders", {
|
|
274
|
-
productId: "prod_1",
|
|
275
|
-
qty: 2
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
kit.done(`Order created: ${res.data.id}`);
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
// Inspect request history
|
|
282
|
-
await kit.test("Check History", async () => {
|
|
283
|
-
const last = kit.http.getLastRequest();
|
|
284
|
-
kit.log(`Last request took ${last?.duration}ms`);
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
**Available methods**:
|
|
290
|
-
- `kit.http.get / post / put / patch / delete`
|
|
291
|
-
- `kit.http.setAuth(type, credentials)` / `clearAuth()`
|
|
292
|
-
- `kit.http.getRequestHistory()` / `getLastRequest()` / `clearHistory()`
|
|
293
|
-
|
|
294
|
-
---
|
|
295
|
-
|
|
296
|
-
## π NEW in v1.2.0: Audit Log
|
|
297
|
-
|
|
298
|
-
Every STORE / SECRET operation is automatically recorded.
|
|
299
|
-
|
|
300
|
-
```typescript
|
|
301
|
-
await newLabs("Audit Demo", async (kit) => {
|
|
302
|
-
await kit.test("Operations", async () => {
|
|
303
|
-
kit.setStore("userId", "123");
|
|
304
|
-
kit.setSecret("token", "secret");
|
|
305
|
-
kit.getStore("userId");
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
await kit.test("Inspect Audit", async () => {
|
|
309
|
-
const last5 = kit.audit.getLast(5);
|
|
310
|
-
const byKey = kit.audit.filterByKey("token");
|
|
311
|
-
const json = kit.audit.export("json");
|
|
312
|
-
const csv = kit.audit.export("csv");
|
|
313
|
-
|
|
314
|
-
kit.log(`Recorded ${last5.length} operations`);
|
|
315
|
-
});
|
|
316
|
-
});
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
---
|
|
320
|
-
|
|
321
|
-
## π§Ή clearStore() & clearSecret()
|
|
322
|
-
|
|
323
|
-
```typescript
|
|
324
|
-
// Clear specific key
|
|
325
|
-
kit.clearStore("authToken");
|
|
326
|
-
kit.clearSecret("accessToken");
|
|
327
|
-
|
|
328
|
-
// Clear everything
|
|
329
|
-
kit.clearStore();
|
|
330
|
-
kit.clearSecret();
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
---
|
|
334
|
-
|
|
335
|
-
## β° TTL Support (Time-To-Live)
|
|
336
|
-
|
|
337
|
-
```typescript
|
|
338
|
-
// Auto-expire after 10 seconds
|
|
339
|
-
kit.setStoreWithTTL("sessionId", "sess_abc", 10_000);
|
|
340
|
-
kit.setSecretWithTTL("tempKey", "value", 30_000);
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
---
|
|
344
|
-
|
|
345
|
-
## π οΈ LabContext Methods (v1.2.0)
|
|
346
|
-
|
|
347
|
-
```typescript
|
|
348
|
-
interface LabContext {
|
|
349
|
-
// Testing & Flow
|
|
350
|
-
test(name: string, fn: () => Promise<any>): Promise<any>
|
|
351
|
-
done(msg: string): void
|
|
352
|
-
err(msg: string): void // stops the Lab
|
|
353
|
-
flatErr(msg: string): void // continues
|
|
354
|
-
log(msg: string): void
|
|
355
|
-
warning(msg: string): void
|
|
356
|
-
|
|
357
|
-
// Flow control
|
|
358
|
-
out(): void // exit Lab immediately
|
|
359
|
-
ret(): void // restart Lab (max 3 times)
|
|
360
|
-
|
|
361
|
-
// Persistent STORE
|
|
362
|
-
setStore(key: string, value: any): void
|
|
363
|
-
getStore(key: string): any
|
|
364
|
-
clearStore(key?: string): void
|
|
365
|
-
setStoreWithTTL(key: string, value: any, ttlMs: number): void
|
|
366
|
-
|
|
367
|
-
// Encrypted Secrets (AES-256-GCM)
|
|
368
|
-
setSecret(key: string, value: string): void
|
|
369
|
-
getSecret(key: string): string | undefined
|
|
370
|
-
clearSecret(key?: string): void
|
|
371
|
-
setSecretWithTTL(key: string, value: string, ttlMs: number): void
|
|
372
|
-
|
|
373
|
-
// Namespaces
|
|
374
|
-
namespace(name: string): NamespacedStore
|
|
375
|
-
getNamespace(name: string): NamespacedStore | undefined
|
|
376
|
-
|
|
377
|
-
// Temporary (Lab-local only)
|
|
378
|
-
setTemp(key: string, value: any): void
|
|
379
|
-
getTemp(key: string): any
|
|
380
|
-
|
|
381
|
-
// HTTP Kit
|
|
382
|
-
http: HttpKit
|
|
383
|
-
|
|
384
|
-
// Audit Log
|
|
385
|
-
audit: AuditLog
|
|
386
|
-
}
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
---
|
|
390
|
-
|
|
391
|
-
## π‘ Real-World Example (v1.2.0)
|
|
392
|
-
|
|
393
|
-
```typescript
|
|
21
|
+
```ts
|
|
394
22
|
import { newLabs } from "fenneckit";
|
|
395
23
|
|
|
396
|
-
await newLabs("
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
await kit.test("Register User", async () => {
|
|
401
|
-
const res = await kit.http.post("https://api.shop.com/auth/register", {
|
|
402
|
-
email: "test@example.com",
|
|
403
|
-
password: "secure123"
|
|
24
|
+
await newLabs("User API", async (kit) => {
|
|
25
|
+
await kit.test("Create User", async () => {
|
|
26
|
+
const res = await kit.http.post("https://api.example.com/users", {
|
|
27
|
+
name: "John",
|
|
404
28
|
});
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
kit.setSecret("token", res.data.token); // encrypted
|
|
408
|
-
kit.http.setAuth("bearer", res.data.token);
|
|
409
|
-
|
|
410
|
-
kit.done("User registered");
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
await kit.test("Create Order", async () => {
|
|
414
|
-
const res = await kit.http.post("https://api.shop.com/orders", {
|
|
415
|
-
items: [{ id: "prod_1", qty: 2 }]
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
order.set("id", res.data.orderId);
|
|
419
|
-
order.set("total", res.data.total);
|
|
420
|
-
kit.done(`Order created: ${res.data.orderId}`);
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
await kit.test("Security Cleanup", async () => {
|
|
424
|
-
kit.clearSecret("token"); // remove sensitive data
|
|
425
|
-
kit.done("Secrets cleared");
|
|
29
|
+
kit.setStore("userId", res.data.id);
|
|
30
|
+
kit.done("User created");
|
|
426
31
|
});
|
|
427
32
|
|
|
428
|
-
await kit.test("
|
|
429
|
-
const
|
|
430
|
-
|
|
33
|
+
await kit.test("Get User", async () => {
|
|
34
|
+
const res = await kit.http.get(
|
|
35
|
+
`https://api.example.com/users/${kit.getStore("userId")}`,
|
|
36
|
+
);
|
|
37
|
+
kit.done(`Status ${res.status}`);
|
|
431
38
|
});
|
|
432
39
|
});
|
|
433
40
|
```
|
|
434
41
|
|
|
435
|
-
|
|
42
|
+
## Install
|
|
436
43
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
```
|
|
440
|
-
FILE EXECUTION START
|
|
441
|
-
β
|
|
442
|
-
ββ Lab 1
|
|
443
|
-
β ββ setStore / setSecret / namespace.set β kept across Labs
|
|
444
|
-
β ββ setTemp β kept only for this Lab
|
|
445
|
-
β ββ clearStore / clearSecret β optional cleanup
|
|
446
|
-
β ββ Lab ends β TEMP cleared, everything else remains
|
|
447
|
-
β
|
|
448
|
-
ββ Lab 2
|
|
449
|
-
β ββ getStore / getSecret / namespace.get β works
|
|
450
|
-
β ββ Lab ends β TEMP cleared
|
|
451
|
-
β
|
|
452
|
-
ββ FILE END β STORE + Secrets + Namespaces completely cleared
|
|
44
|
+
```bash
|
|
45
|
+
npm install fenneckit@latest --save-dev
|
|
453
46
|
```
|
|
454
47
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
## π― Best Practices (v1.2.0)
|
|
458
|
-
|
|
459
|
-
1. **Secrets always go into the Vault**
|
|
460
|
-
```typescript
|
|
461
|
-
kit.setSecret("token", token); // β
|
|
462
|
-
kit.setStore("token", token); // β avoid
|
|
463
|
-
```
|
|
464
|
-
|
|
465
|
-
2. **Use namespaces for related data**
|
|
466
|
-
```typescript
|
|
467
|
-
const user = kit.namespace("user");
|
|
468
|
-
user.set("id", id);
|
|
469
|
-
user.set("email", email);
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
3. **Clear sensitive data when no longer needed**
|
|
473
|
-
```typescript
|
|
474
|
-
kit.clearSecret("accessToken");
|
|
475
|
-
kit.clearStore("creditCard");
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
4. **Check before use**
|
|
479
|
-
```typescript
|
|
480
|
-
const token = kit.getSecret("token");
|
|
481
|
-
if (!token) kit.err("Token missing!");
|
|
482
|
-
```
|
|
48
|
+
Requires Node.js >= 20.
|
|
483
49
|
|
|
484
|
-
|
|
50
|
+
## Run
|
|
485
51
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
- `β
` / `π’` Success
|
|
491
|
-
- `β` / `π΄` Failed (Lab continues)
|
|
492
|
-
- `π` Error (Lab stops)
|
|
493
|
-
- `β οΈ` / `π‘` Warning
|
|
494
|
-
- `π` / `βͺ` Info
|
|
495
|
-
- `βοΈ` Store operation
|
|
496
|
-
- `π` Secret operation
|
|
497
|
-
- `β±οΈ` Temp operation
|
|
498
|
-
- `π§Ή` Clear operation
|
|
499
|
-
- `β°` TTL expiration
|
|
500
|
-
|
|
501
|
-
---
|
|
502
|
-
|
|
503
|
-
## πͺ Perfect For
|
|
504
|
-
|
|
505
|
-
- Backend API testing (Express, Fastify, NestJS, etc.)
|
|
506
|
-
- Security-conscious test flows (tokens, keys)
|
|
507
|
-
- Database migration validation
|
|
508
|
-
- Microservice chaining
|
|
509
|
-
- Pre-deployment smoke checks
|
|
510
|
-
- State management + audit trails
|
|
511
|
-
- Data pipeline validation
|
|
512
|
-
|
|
513
|
-
---
|
|
514
|
-
|
|
515
|
-
## π Version History
|
|
52
|
+
```bash
|
|
53
|
+
npx fenneckit # auto-discover and run all *.labs.js files
|
|
54
|
+
npx fenneckit user.labs.js # run one file
|
|
55
|
+
```
|
|
516
56
|
|
|
517
|
-
|
|
518
|
-
- π **Secret Vault** β AES-256-GCM encryption
|
|
519
|
-
- ποΈ **Namespaced Store**
|
|
520
|
-
- π **Audit Log** (JSON / CSV export)
|
|
521
|
-
- π‘ **HTTP Kit** with auth, retry, history
|
|
522
|
-
- β° **TTL support** for STORE & Secrets
|
|
523
|
-
- Improved LabContext API
|
|
57
|
+
## Concepts
|
|
524
58
|
|
|
525
|
-
|
|
526
|
-
-
|
|
527
|
-
-
|
|
59
|
+
- **Lab**: a group of sequential tests working toward one goal.
|
|
60
|
+
- **Test**: one step inside a lab, run with `kit.test(name, fn)`.
|
|
61
|
+
- **Storage**: data shared between tests of the same lab (see below).
|
|
528
62
|
|
|
529
|
-
|
|
530
|
-
- Core Lab functionality
|
|
531
|
-
- STORE & TEMP storage
|
|
532
|
-
- Zero config runner
|
|
533
|
-
- Report generation
|
|
63
|
+
## Features
|
|
534
64
|
|
|
535
|
-
|
|
65
|
+
| Feature | What it does | Docs |
|
|
66
|
+
| ------------ | ------------------------------------------ | ------------------------------------------ |
|
|
67
|
+
| π¬ Labs | Group tests into workflows | [Getting started](docs/getting-started.md) |
|
|
68
|
+
| π¦ STORE | Plain shared data | [Storage](docs/storage.md) |
|
|
69
|
+
| π SECRET | AES-256-GCM encrypted data | [Storage](docs/storage.md#secret) |
|
|
70
|
+
| ποΈ NAMESPACE | Grouped data per entity | [Storage](docs/storage.md#namespace) |
|
|
71
|
+
| β±οΈ TEMP | Lab-local scratch data | [Storage](docs/storage.md#temp) |
|
|
72
|
+
| β° TTL | Auto-expiring STORE/SECRET values | [Storage](docs/storage.md#ttl) |
|
|
73
|
+
| π‘ HTTP Kit | Client with auth, retry, history | [HTTP Kit](docs/http-kit.md) |
|
|
74
|
+
| π Audit Log | Trace store/secret access, export JSON/CSV | [Audit log](docs/audit-log.md) |
|
|
536
75
|
|
|
537
|
-
##
|
|
76
|
+
## Documentation
|
|
538
77
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
78
|
+
- [Getting started](docs/getting-started.md)
|
|
79
|
+
- [Storage (STORE, SECRET, NAMESPACE, TEMP, TTL)](docs/storage.md)
|
|
80
|
+
- [HTTP Kit](docs/http-kit.md)
|
|
81
|
+
- [Audit log](docs/audit-log.md)
|
|
82
|
+
- [API reference](docs/api-reference.md)
|
|
83
|
+
- [Best practices](docs/best-practices.md)
|
|
84
|
+
- [Changelog](docs/changelog.md)
|
|
544
85
|
|
|
545
|
-
|
|
86
|
+
## License
|
|
546
87
|
|
|
547
|
-
|
|
88
|
+
MIT. Copyright Β© 2026 Lasith Ruwantha Amrwansha.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# API reference
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
import { newLabs } from "fenneckit";
|
|
5
|
+
|
|
6
|
+
await newLabs(name: string, fn: (kit: LabContext) => void | Promise<void>);
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
`fn` receives `kit` (the `LabContext`).
|
|
10
|
+
|
|
11
|
+
## Testing and flow
|
|
12
|
+
|
|
13
|
+
| Method | Description |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `kit.test(name, fn)` | Run a named test; returns the test function's result |
|
|
16
|
+
| `kit.done(msg)` | Mark passed |
|
|
17
|
+
| `kit.log(msg)` | Info message |
|
|
18
|
+
| `kit.warning(msg)` | Warning |
|
|
19
|
+
| `kit.flatErr(msg)` | Failure, continue the lab |
|
|
20
|
+
| `kit.err(msg)` | Failure, stop the lab |
|
|
21
|
+
| `kit.out()` | Exit the lab immediately |
|
|
22
|
+
| `kit.ret()` | Restart the lab (max 3 times) |
|
|
23
|
+
|
|
24
|
+
If a test throws an unexpected error it is printed as a bug exception and the lab is marked failed.
|
|
25
|
+
|
|
26
|
+
## STORE
|
|
27
|
+
|
|
28
|
+
| Method | Description |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| `setStore(key, value)` | Save |
|
|
31
|
+
| `getStore(key)` | Read |
|
|
32
|
+
| `clearStore(key?)` | Clear one key or all |
|
|
33
|
+
| `setStoreWithTTL(key, value, ms)` | Save with expiry |
|
|
34
|
+
|
|
35
|
+
## SECRET
|
|
36
|
+
|
|
37
|
+
| Method | Description |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| `setSecret(key, value)` | Save encrypted |
|
|
40
|
+
| `getSecret(key)` | Read decrypted (`string | undefined`) |
|
|
41
|
+
| `clearSecret(key?)` | Clear one key or all |
|
|
42
|
+
| `setSecretWithTTL(key, value, ms)` | Save with expiry |
|
|
43
|
+
|
|
44
|
+
## NAMESPACE
|
|
45
|
+
|
|
46
|
+
| Method | Description |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `namespace(name)` | Create or get a namespace |
|
|
49
|
+
| `getNamespace(name)` | Get an existing namespace |
|
|
50
|
+
| `ns.set / get / delete / clear / keys / entries / has / toJSON` | Namespace operations |
|
|
51
|
+
|
|
52
|
+
## TEMP
|
|
53
|
+
|
|
54
|
+
| Method | Description |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| `setTemp(key, value)` | Save temp value |
|
|
57
|
+
| `getTemp(key)` | Read temp value |
|
|
58
|
+
|
|
59
|
+
## HTTP
|
|
60
|
+
|
|
61
|
+
See [HTTP Kit](http-kit.md): `get`, `post`, `put`, `patch`, `delete`, `setAuth`, `clearAuth`, `getRequestHistory`, `getLastRequest`, `clearHistory`.
|
|
62
|
+
|
|
63
|
+
## Audit
|
|
64
|
+
|
|
65
|
+
See [Audit log](audit-log.md): `getLog`, `getLast`, `filterByKey`, `filterByOperation`, `filterByNamespace`, `export`, `clear`.
|
|
66
|
+
|
|
67
|
+
## CLI
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx fenneckit # run all *.labs.js
|
|
71
|
+
npx fenneckit <file> # run one file
|
|
72
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Audit log
|
|
2
|
+
|
|
3
|
+
`kit.audit` records access to STORE and SECRET values so you can trace a workflow.
|
|
4
|
+
|
|
5
|
+
## What is recorded
|
|
6
|
+
|
|
7
|
+
| Call | Operation |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `setStore` | `set` |
|
|
10
|
+
| `getStore` | `get` |
|
|
11
|
+
| `setSecret` | `setSecret` |
|
|
12
|
+
| `getSecret` | `getSecret` |
|
|
13
|
+
|
|
14
|
+
Namespaces, TEMP, and `clear*` calls are not recorded. TTL setters are recorded through their underlying set call.
|
|
15
|
+
|
|
16
|
+
## Entry fields
|
|
17
|
+
|
|
18
|
+
`timestamp`, `lab`, `test`, `operation`, `key`, `dataType`, `success`, and optionally `namespace`. Secret values are never stored.
|
|
19
|
+
|
|
20
|
+
## Querying
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
kit.audit.getLog(); // all entries (copy)
|
|
24
|
+
kit.audit.getLast(5); // last 5 entries
|
|
25
|
+
kit.audit.filterByKey("token");
|
|
26
|
+
kit.audit.filterByOperation("setSecret");
|
|
27
|
+
kit.audit.filterByNamespace("user");
|
|
28
|
+
kit.audit.clear();
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Export
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
const json = kit.audit.export("json"); // pretty-printed JSON
|
|
35
|
+
const csv = kit.audit.export("csv"); // header: timestamp,lab,test,operation,namespace,key,dataType,success
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Example
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
await kit.test("Review Audit", async () => {
|
|
42
|
+
const last = kit.audit.getLast(5);
|
|
43
|
+
const tokenOps = kit.audit.filterByKey("token");
|
|
44
|
+
kit.log(`${last.length} recent, ${tokenOps.length} on "token"`);
|
|
45
|
+
kit.done("Audit reviewed");
|
|
46
|
+
});
|
|
47
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Best practices
|
|
2
|
+
|
|
3
|
+
1. **Use STORE for regular data** (IDs, names).
|
|
4
|
+
2. **Use SECRET for sensitive data** (tokens, passwords, refresh tokens). Never put them in STORE.
|
|
5
|
+
3. **Use NAMESPACE for organization**: `const user = kit.namespace("user")` instead of `userId`, `userEmail`, ... keys.
|
|
6
|
+
4. **Check before using**:
|
|
7
|
+
```ts
|
|
8
|
+
const token = kit.getSecret("token");
|
|
9
|
+
if (!token) kit.err("Token is missing!");
|
|
10
|
+
```
|
|
11
|
+
5. **Clean up secrets** in a final test:
|
|
12
|
+
```ts
|
|
13
|
+
kit.clearSecret("accessToken");
|
|
14
|
+
kit.http.clearAuth();
|
|
15
|
+
```
|
|
16
|
+
6. **Check `res.status` yourself.** HTTP error responses are returned, not thrown.
|
|
17
|
+
7. **Use `flatErr` for non-blocking checks** (e.g. an optional email step) and `err` when later tests cannot continue.
|
|
18
|
+
8. **Use the audit log** for debugging and traceability, and clear HTTP history before exporting it.
|
|
19
|
+
9. **One lab, one goal.** Keep labs small and independent, since nothing is shared between labs.
|
|
20
|
+
|
|
21
|
+
## Good fits
|
|
22
|
+
|
|
23
|
+
API testing, security checks, migration validation, microservice chains, pre-deployment smoke tests, ETL/data pipeline checks, and multi-step integration tests.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install fenneckit@latest --save-dev
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Node.js >= 20 is required. FennecKit is an ES module package (`"type": "module"`).
|
|
10
|
+
|
|
11
|
+
## Your first lab
|
|
12
|
+
|
|
13
|
+
Create `user.labs.js`:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { newLabs } from "fenneckit";
|
|
17
|
+
|
|
18
|
+
await newLabs("User API Test", async (kit) => {
|
|
19
|
+
await kit.test("Step 1: Create User", async () => {
|
|
20
|
+
kit.setStore("userId", "usr_123");
|
|
21
|
+
kit.done("Stored user ID");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await kit.test("Step 2: Read User", async () => {
|
|
25
|
+
const userId = kit.getStore("userId");
|
|
26
|
+
kit.log(`Using user: ${userId}`);
|
|
27
|
+
kit.done("Retrieved user");
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Run
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx fenneckit # run every *.labs.js file
|
|
36
|
+
npx fenneckit user.labs.js # run a single file
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Core ideas
|
|
40
|
+
|
|
41
|
+
| Term | Meaning |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| Lab | A collection of sequential tests toward one goal |
|
|
44
|
+
| Test | One step, run with `kit.test(name, fn)` |
|
|
45
|
+
| Storage | Data shared by all tests in the same lab |
|
|
46
|
+
|
|
47
|
+
Example lab layout:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
π¬ Lab: Payment Processing
|
|
51
|
+
ββ Test 1: Validate Card
|
|
52
|
+
ββ Test 2: Charge Card
|
|
53
|
+
ββ Test 3: Generate Invoice
|
|
54
|
+
ββ Test 4: Send Receipt Email
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Reporting results
|
|
58
|
+
|
|
59
|
+
Inside a test, report status with:
|
|
60
|
+
|
|
61
|
+
| Method | Effect |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| `kit.done(msg)` | Test passed (π’) |
|
|
64
|
+
| `kit.log(msg)` | Info message (βͺ) |
|
|
65
|
+
| `kit.warning(msg)` | Warning (π‘) |
|
|
66
|
+
| `kit.flatErr(msg)` | Failure, lab **continues** (π΄) |
|
|
67
|
+
| `kit.err(msg)` | Failure, lab **stops** (π΄) |
|
|
68
|
+
|
|
69
|
+
## Flow control
|
|
70
|
+
|
|
71
|
+
- `kit.out()` exits the lab immediately.
|
|
72
|
+
- `kit.ret()` restarts the whole lab, up to 3 times. After that it calls `kit.err("Restart limit exceeded (max 3 attempts)")`.
|
|
73
|
+
|
|
74
|
+
## Isolation
|
|
75
|
+
|
|
76
|
+
Each `newLabs(...)` call has its own isolated STORE, SECRET, NAMESPACE, TEMP, HTTP client and audit log. Nothing is shared between labs or files.
|
|
77
|
+
|
|
78
|
+
## Next
|
|
79
|
+
|
|
80
|
+
- [Storage](storage.md)
|
|
81
|
+
- [HTTP Kit](http-kit.md)
|
|
82
|
+
- [API reference](api-reference.md)
|
package/docs/http-kit.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# HTTP Kit
|
|
2
|
+
|
|
3
|
+
`kit.http` is a built-in client (based on `fetch`) with auth, timeout, retry and request history.
|
|
4
|
+
|
|
5
|
+
## Methods
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
kit.http.get(url, options?)
|
|
9
|
+
kit.http.post(url, body, options?)
|
|
10
|
+
kit.http.put(url, body, options?)
|
|
11
|
+
kit.http.patch(url, body, options?)
|
|
12
|
+
kit.http.delete(url, options?)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Bodies that are not strings are sent as JSON. Default headers: `Content-Type: application/json` and `User-Agent: FennecKit/1.2.0`.
|
|
16
|
+
|
|
17
|
+
## Response
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
{
|
|
21
|
+
status: number,
|
|
22
|
+
statusText: string,
|
|
23
|
+
headers: Record<string, string>,
|
|
24
|
+
data: any, // parsed JSON if content-type is application/json, otherwise text
|
|
25
|
+
raw: Response
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Options
|
|
30
|
+
|
|
31
|
+
| Option | Default | Description |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| `headers` | none | Extra headers, override defaults |
|
|
34
|
+
| `timeout` | `30000` | Timeout in ms (uses `AbortController`) |
|
|
35
|
+
| `retry.max` | `1` | Total number of **attempts** |
|
|
36
|
+
| `retry.delay` | `1000` | Wait in ms between attempts |
|
|
37
|
+
| `followRedirects` | `true` | Set `false` for manual redirects |
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
const res = await kit.http.get("https://api.example.com/users", {
|
|
41
|
+
timeout: 5000,
|
|
42
|
+
retry: { max: 3, delay: 1000 },
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Retries happen only on network errors (thrown by `fetch`). An HTTP 4xx/5xx response is returned normally, so check `res.status` yourself.
|
|
47
|
+
|
|
48
|
+
## Authentication
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
kit.http.setAuth("bearer", token); // Authorization: Bearer <token>
|
|
52
|
+
kit.http.setAuth("basic", base64Creds); // Authorization: Basic <base64Creds>
|
|
53
|
+
kit.http.setAuth("api-key", key); // X-API-Key: <key>
|
|
54
|
+
kit.http.clearAuth();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
For `basic`, pass the credentials already base64-encoded.
|
|
58
|
+
|
|
59
|
+
## Request history
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
kit.http.getRequestHistory(); // TracedRequest[]
|
|
63
|
+
kit.http.getLastRequest(); // TracedRequest | undefined
|
|
64
|
+
kit.http.clearHistory();
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Each entry contains: `id`, `timestamp`, `method`, `url`, `status` (0 on failure), `duration`, `headers`, `requestBody`, `responseBody`, and `error` when the request failed.
|
|
68
|
+
|
|
69
|
+
> History stores request headers (including auth headers) and bodies as-is. Call `kit.http.clearHistory()` before exporting or printing it if it may contain credentials.
|
|
70
|
+
|
|
71
|
+
When all attempts fail, the last error is thrown.
|
package/docs/storage.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Storage
|
|
2
|
+
|
|
3
|
+
Every lab has five kinds of in-memory storage.
|
|
4
|
+
|
|
5
|
+
| Level | Use for | Encrypted | Audited | Lifetime |
|
|
6
|
+
| --- | --- | --- | --- | --- |
|
|
7
|
+
| STORE | IDs, names, plain values | No | Yes (set/get) | Until lab ends |
|
|
8
|
+
| SECRET | Tokens, passwords | Yes | Yes (set/get) | Until lab ends |
|
|
9
|
+
| NAMESPACE | Grouped entity data | No | No | Until lab ends |
|
|
10
|
+
| TEMP | Scratch values | No | No | Until lab ends |
|
|
11
|
+
| TTL variants | Auto-expiring STORE/SECRET | Same as base | Same as base | Until TTL or lab end |
|
|
12
|
+
|
|
13
|
+
Storage is per lab: nothing is shared across labs or files.
|
|
14
|
+
|
|
15
|
+
## STORE
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
await kit.test("Create User", async () => {
|
|
19
|
+
const res = await kit.http.post("/users", { name: "Alice" });
|
|
20
|
+
kit.setStore("userId", res.data.id);
|
|
21
|
+
kit.done("User created");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await kit.test("Get User", async () => {
|
|
25
|
+
const userId = kit.getStore("userId");
|
|
26
|
+
// ...
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- `kit.setStore(key, value)`
|
|
31
|
+
- `kit.getStore(key)`
|
|
32
|
+
- `kit.clearStore(key)` removes one key; `kit.clearStore()` removes everything.
|
|
33
|
+
|
|
34
|
+
## SECRET
|
|
35
|
+
|
|
36
|
+
Use SECRET for anything sensitive. Values are encrypted in memory with AES-256-GCM (random 16-byte IV and auth tag per value, key derived with scrypt) and decrypted on read.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
kit.setSecret("accessToken", res.data.accessToken);
|
|
40
|
+
const token = kit.getSecret("accessToken"); // string | undefined
|
|
41
|
+
kit.http.setAuth("bearer", token!);
|
|
42
|
+
kit.clearSecret("accessToken");
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- `kit.setSecret(key, value)`, `kit.getSecret(key)`
|
|
46
|
+
- `kit.clearSecret(key)` or `kit.clearSecret()` for all
|
|
47
|
+
- Secret **values** are never written to the lab log or audit log. Only the key name is.
|
|
48
|
+
- If decryption fails, `getSecret` logs an error and returns `undefined`.
|
|
49
|
+
|
|
50
|
+
> Note: the vault protects values held in memory. It uses a built-in default password, so treat it as protection against accidental exposure (logs, dumps), not as a security boundary.
|
|
51
|
+
|
|
52
|
+
## NAMESPACE
|
|
53
|
+
|
|
54
|
+
Group related values instead of using prefixed keys.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const user = kit.namespace("user"); // create or get
|
|
58
|
+
const order = kit.namespace("order");
|
|
59
|
+
|
|
60
|
+
user.set("id", "usr_123");
|
|
61
|
+
order.set("total", 299.99);
|
|
62
|
+
|
|
63
|
+
user.get("id");
|
|
64
|
+
kit.getNamespace("user"); // existing namespace, or undefined
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Namespace methods: `set`, `get`, `delete`, `clear(key?)`, `keys`, `entries`, `has`, `toJSON`.
|
|
68
|
+
|
|
69
|
+
## TEMP
|
|
70
|
+
|
|
71
|
+
Scratch values for short-lived data such as timers.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
kit.setTemp("startTime", Date.now());
|
|
75
|
+
await kit.http.get("/api/heavy-endpoint");
|
|
76
|
+
const elapsed = Date.now() - kit.getTemp("startTime");
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
TEMP is cleared when the lab finishes. Do not rely on it as per-test storage; use unique keys or STORE for anything you want to control.
|
|
80
|
+
|
|
81
|
+
## TTL
|
|
82
|
+
|
|
83
|
+
Values that expire automatically.
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
kit.setStoreWithTTL("sessionId", "sess_123", 30_000); // 30 s
|
|
87
|
+
kit.setSecretWithTTL("tempToken", "token", 60_000); // 60 s
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Setting the same key again resets its timer. Expiry is written to the lab log. Pending timers are cleared when the lab ends.
|
package/index.d.ts
ADDED
package/index.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACR,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAE5C,OAAO,EACL,OAAO,EACP,OAAO,EACR,CAAA;AACD,YAAY,EACV,aAAa,EACb,YAAY,EACZ,cAAc,EACd,UAAU,EACX,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fenneckit.d.ts","sourceRoot":"","sources":["../../src/libs/fenneckit.ts"],"names":[],"mappings":""}
|
package/libs/labs.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { TracedRequest, HttpResponse, RequestOptions, AuditEntry } from "../types.js";
|
|
2
|
+
declare class NamespacedStore {
|
|
3
|
+
private data;
|
|
4
|
+
private name;
|
|
5
|
+
constructor(name: string);
|
|
6
|
+
set(key: string, value: any): void;
|
|
7
|
+
get(key: string): any;
|
|
8
|
+
delete(key: string): void;
|
|
9
|
+
clear(key?: string): void;
|
|
10
|
+
keys(): string[];
|
|
11
|
+
entries(): [string, any][];
|
|
12
|
+
has(key: string): boolean;
|
|
13
|
+
toJSON(): Record<string, any>;
|
|
14
|
+
}
|
|
15
|
+
declare class AuditLog {
|
|
16
|
+
private entries;
|
|
17
|
+
record(entry: AuditEntry): void;
|
|
18
|
+
getLog(): AuditEntry[];
|
|
19
|
+
getLast(n: number): AuditEntry[];
|
|
20
|
+
filterByKey(key: string): AuditEntry[];
|
|
21
|
+
filterByOperation(op: AuditEntry["operation"]): AuditEntry[];
|
|
22
|
+
filterByNamespace(ns: string): AuditEntry[];
|
|
23
|
+
export(format: "json" | "csv"): string;
|
|
24
|
+
clear(): void;
|
|
25
|
+
}
|
|
26
|
+
interface LabContext {
|
|
27
|
+
out: () => void;
|
|
28
|
+
ret: () => void;
|
|
29
|
+
flatErr: (msg: string) => void;
|
|
30
|
+
err: (msg: string) => void;
|
|
31
|
+
done: (msg: string) => void;
|
|
32
|
+
log: (msg: string) => void;
|
|
33
|
+
warning: (msg: string) => void;
|
|
34
|
+
setStore: (key: string, value: any) => void;
|
|
35
|
+
getStore: (key: string) => any;
|
|
36
|
+
clearStore: (key?: string) => void;
|
|
37
|
+
setSecret: (key: string, value: string) => void;
|
|
38
|
+
getSecret: (key: string) => string | undefined;
|
|
39
|
+
clearSecret: (key?: string) => void;
|
|
40
|
+
namespace: (name: string) => NamespacedStore;
|
|
41
|
+
getNamespace: (name: string) => NamespacedStore | undefined;
|
|
42
|
+
setTemp: (key: string, value: any) => void;
|
|
43
|
+
getTemp: (key: string) => any;
|
|
44
|
+
http: HttpKit;
|
|
45
|
+
audit: AuditLog;
|
|
46
|
+
setStoreWithTTL: (key: string, value: any, ttlMs: number) => void;
|
|
47
|
+
setSecretWithTTL: (key: string, value: string, ttlMs: number) => void;
|
|
48
|
+
test: <T>(testName: string, testFn: () => T | Promise<T>) => Promise<T>;
|
|
49
|
+
}
|
|
50
|
+
declare class HttpKit {
|
|
51
|
+
private authHeader;
|
|
52
|
+
private requestHistory;
|
|
53
|
+
private baseHeaders;
|
|
54
|
+
setAuth(type: "bearer" | "basic" | "api-key", credentials: string): void;
|
|
55
|
+
clearAuth(): void;
|
|
56
|
+
private getAuthHeader;
|
|
57
|
+
private makeRequest;
|
|
58
|
+
private recordRequest;
|
|
59
|
+
get(url: string, options?: RequestOptions): Promise<HttpResponse>;
|
|
60
|
+
post(url: string, body: any, options?: RequestOptions): Promise<HttpResponse>;
|
|
61
|
+
put(url: string, body: any, options?: RequestOptions): Promise<HttpResponse>;
|
|
62
|
+
patch(url: string, body: any, options?: RequestOptions): Promise<HttpResponse>;
|
|
63
|
+
delete(url: string, options?: RequestOptions): Promise<HttpResponse>;
|
|
64
|
+
getRequestHistory(): TracedRequest[];
|
|
65
|
+
clearHistory(): void;
|
|
66
|
+
getLastRequest(): TracedRequest | undefined;
|
|
67
|
+
}
|
|
68
|
+
export declare function newLabs(name: string, fn: (kit: LabContext) => void | Promise<void>, retCount?: number): Promise<void>;
|
|
69
|
+
export {};
|
|
70
|
+
//# sourceMappingURL=labs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"labs.d.ts","sourceRoot":"","sources":["../../src/libs/labs.ts"],"names":[],"mappings":"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"}
|
package/libs/labs.js
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
import { createCipheriv, createDecipheriv, randomBytes, scryptSync, } from "crypto";
|
|
8
8
|
import report from "../utility/report.js";
|
|
9
9
|
import path from "path";
|
|
10
|
+
/*===========================
|
|
11
|
+
All calass
|
|
12
|
+
=============================*/
|
|
10
13
|
class SecretVault {
|
|
11
14
|
vault = new Map();
|
|
12
15
|
masterKey;
|
|
@@ -291,6 +294,7 @@ class HttpKit {
|
|
|
291
294
|
return this.requestHistory[this.requestHistory.length - 1];
|
|
292
295
|
}
|
|
293
296
|
}
|
|
297
|
+
// ====================== Lab Context ======================
|
|
294
298
|
export async function newLabs(name, fn, retCount = 0) {
|
|
295
299
|
let logs = `## ${name} π\n\n\`\`\`\n`;
|
|
296
300
|
let currentTest = "";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface TestContext {
|
|
2
|
+
succes: (msg: string) => void;
|
|
3
|
+
err: (msg: string) => never;
|
|
4
|
+
log: (msg: string) => void;
|
|
5
|
+
worrn: (msg: string) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function testing<T>(fn: (t: TestContext) => T | Promise<T>): Promise<T | void>;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=testing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../src/libs/testing.ts"],"names":[],"mappings":"AAiCA,UAAU,WAAW;IACnB,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,KAAK,CAAC;IAC5B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAKD,wBAAsB,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,WAAW,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAc1F"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fenneckit",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "A tiny, fast & adorable testing kit. Lightweight test framework with zero config β easy to hold, easy to run.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"testing",
|
|
@@ -29,6 +29,13 @@
|
|
|
29
29
|
"author": "lasith ruwantha amrwansha",
|
|
30
30
|
"type": "module",
|
|
31
31
|
"main": "index.js",
|
|
32
|
+
"types":"./dist/index.d.ts",
|
|
33
|
+
"exports":{
|
|
34
|
+
".":{
|
|
35
|
+
"types":"./dist/index.d.ts",
|
|
36
|
+
"import": "./dist/index.js"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
32
39
|
"scripts": {
|
|
33
40
|
"eslint": "eslint .",
|
|
34
41
|
"build": "tsc",
|
|
@@ -52,5 +59,8 @@
|
|
|
52
59
|
"tsx": "^4.23.13",
|
|
53
60
|
"typescript": "^6.0.3",
|
|
54
61
|
"typescript-eslint": "^8.70.0"
|
|
62
|
+
},
|
|
63
|
+
"engines":{
|
|
64
|
+
"node":">=20"
|
|
55
65
|
}
|
|
56
66
|
}
|
package/types.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface HttpResponse<T = any> {
|
|
2
|
+
status: number;
|
|
3
|
+
statusText: string;
|
|
4
|
+
headers: Record<string, string>;
|
|
5
|
+
data: T;
|
|
6
|
+
raw: Response;
|
|
7
|
+
}
|
|
8
|
+
export interface RequestOptions {
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
timeout?: number;
|
|
11
|
+
retry?: {
|
|
12
|
+
max: number;
|
|
13
|
+
delay: number;
|
|
14
|
+
};
|
|
15
|
+
followRedirects?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface TracedRequest {
|
|
18
|
+
id: string;
|
|
19
|
+
timestamp: string;
|
|
20
|
+
method: string;
|
|
21
|
+
url: string;
|
|
22
|
+
status: number;
|
|
23
|
+
duration: number;
|
|
24
|
+
headers: Record<string, string>;
|
|
25
|
+
requestBody?: any;
|
|
26
|
+
responseBody?: any;
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface AuditEntry {
|
|
30
|
+
timestamp: string;
|
|
31
|
+
lab: string;
|
|
32
|
+
test: string;
|
|
33
|
+
operation: "set" | "get" | "delete" | "clear" | "setSecret" | "getSecret";
|
|
34
|
+
namespace?: string;
|
|
35
|
+
key: string;
|
|
36
|
+
dataType: string;
|
|
37
|
+
success: boolean;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=types.d.ts.map
|
package/types.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,GAAG;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,CAAC,CAAC;IACR,GAAG,EAAE,QAAQ,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,YAAY,CAAC,EAAE,GAAG,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,GAAG,WAAW,GAAG,WAAW,CAAC;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB"}
|
package/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/utility/report.ts"],"names":[],"mappings":"AAoBA,wBAA8B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAShE"}
|