@futdevpro/fsm-dynamo 1.14.19 → 1.14.21
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/build/_models/control-models/error.control-model.d.ts.map +1 -1
- package/build/_models/control-models/error.control-model.js +3 -0
- package/build/_models/control-models/error.control-model.js.map +1 -1
- package/build/_modules/crypto/_collections/crypto.util.d.ts.map +1 -1
- package/build/_modules/crypto/_collections/crypto.util.edge.spec.d.ts +2 -0
- package/build/_modules/crypto/_collections/crypto.util.edge.spec.d.ts.map +1 -0
- package/build/_modules/crypto/_collections/crypto.util.edge.spec.js +551 -0
- package/build/_modules/crypto/_collections/crypto.util.edge.spec.js.map +1 -0
- package/build/_modules/crypto/_collections/crypto.util.extra.spec.d.ts +2 -0
- package/build/_modules/crypto/_collections/crypto.util.extra.spec.d.ts.map +1 -0
- package/build/_modules/crypto/_collections/crypto.util.extra.spec.js +555 -0
- package/build/_modules/crypto/_collections/crypto.util.extra.spec.js.map +1 -0
- package/build/_modules/crypto/_collections/crypto.util.js +33 -3
- package/build/_modules/crypto/_collections/crypto.util.js.map +1 -1
- package/build/_modules/crypto/_collections/crypto.util.simple.spec.d.ts +2 -0
- package/build/_modules/crypto/_collections/crypto.util.simple.spec.d.ts.map +1 -0
- package/build/_modules/crypto/_collections/crypto.util.simple.spec.js +429 -0
- package/build/_modules/crypto/_collections/crypto.util.simple.spec.js.map +1 -0
- package/futdevpro-fsm-dynamo-01.14.21.tgz +0 -0
- package/package.json +2 -2
- package/src/_models/control-models/error.control-model.ts +4 -0
- package/src/_modules/crypto/_collections/crypto.util.edge.spec.ts +606 -0
- package/src/_modules/crypto/_collections/crypto.util.extra.spec.ts +643 -0
- package/src/_modules/crypto/_collections/crypto.util.simple.spec.ts +513 -0
- package/src/_modules/crypto/_collections/crypto.util.ts +81 -48
- package/build/_modules/crypto/_collections/crypto.util.spec.d.ts +0 -2
- package/build/_modules/crypto/_collections/crypto.util.spec.d.ts.map +0 -1
- package/build/_modules/crypto/_collections/crypto.util.spec.js +0 -1180
- package/build/_modules/crypto/_collections/crypto.util.spec.js.map +0 -1
- package/futdevpro-fsm-dynamo-01.14.19.tgz +0 -0
- package/src/_modules/crypto/_collections/crypto.util.spec.ts +0 -1370
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
import { DyFM_Crypto } from './crypto.util';
|
|
2
|
+
|
|
3
|
+
describe('| DyFM_Crypto extra tests', () => {
|
|
4
|
+
|
|
5
|
+
describe('| TIME MANIPULATION TESTS - Cross-temporal consistency', () => {
|
|
6
|
+
const testKey = 'time-test-key-456';
|
|
7
|
+
const testData = {
|
|
8
|
+
id: 123,
|
|
9
|
+
name: 'time-test',
|
|
10
|
+
timestamp: new Date().toISOString(),
|
|
11
|
+
nested: { value: 'time-dependent-test' }
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
let originalDateNow: () => number;
|
|
15
|
+
let originalDateConstructor: DateConstructor;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
// Store original Date functions
|
|
19
|
+
originalDateNow = Date.now;
|
|
20
|
+
originalDateConstructor = Date;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
// Restore original Date functions
|
|
25
|
+
Date.now = originalDateNow;
|
|
26
|
+
(global as any).Date = originalDateConstructor;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('| should produce identical encrypted output regardless of system time', () => {
|
|
30
|
+
const baseTime = 1609459200000; // 2021-01-01 00:00:00 UTC
|
|
31
|
+
|
|
32
|
+
// Mock Date.now to return fixed time
|
|
33
|
+
Date.now = jasmine.createSpy('Date.now').and.returnValue(baseTime);
|
|
34
|
+
|
|
35
|
+
// Mock Date constructor to return fixed date
|
|
36
|
+
(global as any).Date = jasmine.createSpy('Date').and.callFake((args: any) => {
|
|
37
|
+
if (!args) {
|
|
38
|
+
return new originalDateConstructor(baseTime);
|
|
39
|
+
}
|
|
40
|
+
return new originalDateConstructor(args);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const encrypted1 = DyFM_Crypto.encrypt(testData, testKey);
|
|
44
|
+
const encrypted2 = DyFM_Crypto.encrypt(testData, testKey);
|
|
45
|
+
|
|
46
|
+
expect(encrypted1).toEqual(encrypted2);
|
|
47
|
+
expect(encrypted1).toBeDefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('| should maintain consistency across different time zones', () => {
|
|
51
|
+
const timeZones = [
|
|
52
|
+
0, // UTC
|
|
53
|
+
-5, // EST
|
|
54
|
+
1, // CET
|
|
55
|
+
9, // JST
|
|
56
|
+
-8, // PST
|
|
57
|
+
5.5 // IST
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const results: string[] = [];
|
|
61
|
+
|
|
62
|
+
timeZones.forEach(offset => {
|
|
63
|
+
const baseTime = 1609459200000 + (offset * 3600000); // Adjust for timezone
|
|
64
|
+
Date.now = jasmine.createSpy('Date.now').and.returnValue(baseTime);
|
|
65
|
+
|
|
66
|
+
const encrypted = DyFM_Crypto.encrypt(testData, testKey);
|
|
67
|
+
results.push(encrypted);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// All results should be identical regardless of timezone
|
|
71
|
+
const firstResult = results[0];
|
|
72
|
+
results.forEach(result => {
|
|
73
|
+
expect(result).toEqual(firstResult);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('| should handle time-based data consistently', () => {
|
|
78
|
+
const timeBasedData = {
|
|
79
|
+
createdAt: new Date('2023-01-01T00:00:00Z'),
|
|
80
|
+
updatedAt: new Date('2023-12-31T23:59:59Z'),
|
|
81
|
+
timestamp: Date.now(),
|
|
82
|
+
timeString: new Date().toISOString()
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Test with different system times
|
|
86
|
+
const times = [
|
|
87
|
+
1609459200000, // 2021-01-01
|
|
88
|
+
1640995200000, // 2022-01-01
|
|
89
|
+
1672531200000, // 2023-01-01
|
|
90
|
+
1704067200000 // 2024-01-01
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
const results: string[] = [];
|
|
94
|
+
|
|
95
|
+
times.forEach(time => {
|
|
96
|
+
Date.now = jasmine.createSpy('Date.now').and.returnValue(time);
|
|
97
|
+
const encrypted = DyFM_Crypto.encrypt(timeBasedData, testKey);
|
|
98
|
+
results.push(encrypted);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// All results should be identical
|
|
102
|
+
const firstResult = results[0];
|
|
103
|
+
results.forEach(result => {
|
|
104
|
+
expect(result).toEqual(firstResult);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('| should maintain consistency across date boundaries', () => {
|
|
109
|
+
const boundaryTimes = [
|
|
110
|
+
1609459200000, // 2021-01-01 00:00:00
|
|
111
|
+
1609459259999, // 2021-01-01 00:00:59
|
|
112
|
+
1609459260000, // 2021-01-01 00:01:00
|
|
113
|
+
1609545600000, // 2021-01-02 00:00:00
|
|
114
|
+
1609632000000, // 2021-01-03 00:00:00
|
|
115
|
+
1640995200000, // 2022-01-01 00:00:00
|
|
116
|
+
1672531200000 // 2023-01-01 00:00:00
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
const results: string[] = [];
|
|
120
|
+
|
|
121
|
+
boundaryTimes.forEach(time => {
|
|
122
|
+
Date.now = jasmine.createSpy('Date.now').and.returnValue(time);
|
|
123
|
+
const encrypted = DyFM_Crypto.encrypt(testData, testKey);
|
|
124
|
+
results.push(encrypted);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// All results should be identical
|
|
128
|
+
const firstResult = results[0];
|
|
129
|
+
results.forEach(result => {
|
|
130
|
+
expect(result).toEqual(firstResult);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('| should handle leap year transitions consistently', () => {
|
|
135
|
+
const leapYearTimes = [
|
|
136
|
+
1609459200000, // 2021-01-01 (not leap year)
|
|
137
|
+
1640995200000, // 2022-01-01 (not leap year)
|
|
138
|
+
1672531200000, // 2023-01-01 (not leap year)
|
|
139
|
+
1704067200000, // 2024-01-01 (leap year)
|
|
140
|
+
1735689600000, // 2025-01-01 (not leap year)
|
|
141
|
+
1735689600000 + (365 * 24 * 60 * 60 * 1000), // 2025-12-31
|
|
142
|
+
1735689600000 + (366 * 24 * 60 * 60 * 1000) // 2026-01-01
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
const results: string[] = [];
|
|
146
|
+
|
|
147
|
+
leapYearTimes.forEach(time => {
|
|
148
|
+
Date.now = jasmine.createSpy('Date.now').and.returnValue(time);
|
|
149
|
+
const encrypted = DyFM_Crypto.encrypt(testData, testKey);
|
|
150
|
+
results.push(encrypted);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// All results should be identical
|
|
154
|
+
const firstResult = results[0];
|
|
155
|
+
results.forEach(result => {
|
|
156
|
+
expect(result).toEqual(firstResult);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('| should maintain consistency across daylight saving time transitions', () => {
|
|
161
|
+
// DST transition times (approximate)
|
|
162
|
+
const dstTimes = [
|
|
163
|
+
1615708800000, // 2021-03-14 (DST start)
|
|
164
|
+
1615712400000, // 2021-03-14 01:00 (DST start + 1 hour)
|
|
165
|
+
1636243200000, // 2021-11-07 (DST end)
|
|
166
|
+
1636246800000, // 2021-11-07 01:00 (DST end + 1 hour)
|
|
167
|
+
1648339200000, // 2022-03-13 (DST start)
|
|
168
|
+
1648342800000, // 2022-03-13 01:00 (DST start + 1 hour)
|
|
169
|
+
1668816000000, // 2022-11-06 (DST end)
|
|
170
|
+
1668819600000 // 2022-11-06 01:00 (DST end + 1 hour)
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
const results: string[] = [];
|
|
174
|
+
|
|
175
|
+
dstTimes.forEach(time => {
|
|
176
|
+
Date.now = jasmine.createSpy('Date.now').and.returnValue(time);
|
|
177
|
+
const encrypted = DyFM_Crypto.encrypt(testData, testKey);
|
|
178
|
+
results.push(encrypted);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// All results should be identical
|
|
182
|
+
const firstResult = results[0];
|
|
183
|
+
results.forEach(result => {
|
|
184
|
+
expect(result).toEqual(firstResult);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('| should handle microsecond precision consistently', () => {
|
|
189
|
+
const baseTime = 1609459200000;
|
|
190
|
+
const microsecondOffsets = [
|
|
191
|
+
0,
|
|
192
|
+
1,
|
|
193
|
+
10,
|
|
194
|
+
100,
|
|
195
|
+
1000,
|
|
196
|
+
10000,
|
|
197
|
+
100000,
|
|
198
|
+
999999
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
const results: string[] = [];
|
|
202
|
+
|
|
203
|
+
microsecondOffsets.forEach(offset => {
|
|
204
|
+
const time = baseTime + offset;
|
|
205
|
+
Date.now = jasmine.createSpy('Date.now').and.returnValue(time);
|
|
206
|
+
const encrypted = DyFM_Crypto.encrypt(testData, testKey);
|
|
207
|
+
results.push(encrypted);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// All results should be identical
|
|
211
|
+
const firstResult = results[0];
|
|
212
|
+
results.forEach(result => {
|
|
213
|
+
expect(result).toEqual(firstResult);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('| should maintain consistency across different Date object states', () => {
|
|
218
|
+
const testDataWithDates = {
|
|
219
|
+
date1: new Date('2023-01-01'),
|
|
220
|
+
date2: new Date('2023-12-31'),
|
|
221
|
+
date3: new Date(),
|
|
222
|
+
date4: new Date(0), // Unix epoch
|
|
223
|
+
date5: new Date(8640000000000000), // Max safe date
|
|
224
|
+
date6: new Date(-8640000000000000) // Min safe date
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const results: string[] = [];
|
|
228
|
+
|
|
229
|
+
// Test with different system times
|
|
230
|
+
const systemTimes = [1609459200000, 1640995200000, 1672531200000];
|
|
231
|
+
|
|
232
|
+
systemTimes.forEach(systemTime => {
|
|
233
|
+
Date.now = jasmine.createSpy('Date.now').and.returnValue(systemTime);
|
|
234
|
+
const encrypted = DyFM_Crypto.encrypt(testDataWithDates, testKey);
|
|
235
|
+
results.push(encrypted);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// All results should be identical
|
|
239
|
+
const firstResult = results[0];
|
|
240
|
+
results.forEach(result => {
|
|
241
|
+
expect(result).toEqual(firstResult);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('| should handle time-based encryption/decryption cycles consistently', () => {
|
|
246
|
+
const cycles = 10;
|
|
247
|
+
const results: string[] = [];
|
|
248
|
+
|
|
249
|
+
for (let i = 0; i < cycles; i++) {
|
|
250
|
+
// Simulate different system times
|
|
251
|
+
const time = 1609459200000 + (i * 86400000); // Add 1 day each cycle
|
|
252
|
+
Date.now = jasmine.createSpy('Date.now').and.returnValue(time);
|
|
253
|
+
|
|
254
|
+
const encrypted = DyFM_Crypto.encrypt(testData, testKey);
|
|
255
|
+
results.push(encrypted);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// All results should be identical
|
|
259
|
+
const firstResult = results[0];
|
|
260
|
+
results.forEach(result => {
|
|
261
|
+
expect(result).toEqual(firstResult);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('| should maintain consistency with real-world time scenarios', () => {
|
|
266
|
+
const realWorldScenarios = [
|
|
267
|
+
{ name: 'New Year 2021', time: 1609459200000 },
|
|
268
|
+
{ name: 'Leap Day 2020', time: 1583020800000 },
|
|
269
|
+
{ name: 'DST Start 2021', time: 1615708800000 },
|
|
270
|
+
{ name: 'DST End 2021', time: 1636243200000 },
|
|
271
|
+
{ name: 'Midnight UTC', time: 1609459200000 },
|
|
272
|
+
{ name: 'Noon UTC', time: 1609459200000 + (12 * 3600000) },
|
|
273
|
+
{ name: 'End of Day', time: 1609459200000 + (23 * 3600000) + (59 * 60000) + (59 * 1000) }
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
const results: string[] = [];
|
|
277
|
+
|
|
278
|
+
realWorldScenarios.forEach(scenario => {
|
|
279
|
+
Date.now = jasmine.createSpy('Date.now').and.returnValue(scenario.time);
|
|
280
|
+
const encrypted = DyFM_Crypto.encrypt(testData, testKey);
|
|
281
|
+
results.push(encrypted);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// All results should be identical
|
|
285
|
+
const firstResult = results[0];
|
|
286
|
+
results.forEach((result, index) => {
|
|
287
|
+
expect(result).toEqual(firstResult);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe('| ERROR HANDLING TESTS - Comprehensive validation', () => {
|
|
293
|
+
const validKey = 'valid-test-key-123';
|
|
294
|
+
const validData = { id: 1, name: 'test' };
|
|
295
|
+
|
|
296
|
+
describe('| Encryption Error Cases', () => {
|
|
297
|
+
it('| should throw descriptive error when encrypting undefined data', () => {
|
|
298
|
+
expect(() => DyFM_Crypto.encrypt(undefined, validKey)).toThrow();
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('| should throw descriptive error when encrypting null data', () => {
|
|
302
|
+
expect(() => DyFM_Crypto.encrypt(null, validKey)).toThrow();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('| should throw descriptive error when encrypting empty string', () => {
|
|
306
|
+
expect(() => DyFM_Crypto.encrypt('', validKey)).toThrow();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('| should allow empty object for backward compatibility', () => {
|
|
310
|
+
expect(() => DyFM_Crypto.encrypt({}, validKey)).not.toThrow();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('| should allow empty array for backward compatibility', () => {
|
|
314
|
+
expect(() => DyFM_Crypto.encrypt([], validKey)).not.toThrow();
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('| should throw descriptive error when key is missing', () => {
|
|
318
|
+
expect(() => DyFM_Crypto.encrypt(validData, undefined as any)).toThrow();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('| should throw descriptive error when key is null', () => {
|
|
322
|
+
expect(() => DyFM_Crypto.encrypt(validData, null as any)).toThrow();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('| should throw descriptive error when key is not a string', () => {
|
|
326
|
+
expect(() => DyFM_Crypto.encrypt(validData, 123 as any)).toThrow();
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('| should throw descriptive error when key is empty string', () => {
|
|
330
|
+
expect(() => DyFM_Crypto.encrypt(validData, '')).toThrow();
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('| should throw descriptive error when key is only whitespace', () => {
|
|
334
|
+
expect(() => DyFM_Crypto.encrypt(validData, ' ')).toThrow();
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('| should warn about short keys but still work', () => {
|
|
338
|
+
// This test just ensures encryption works with short keys
|
|
339
|
+
// Note: Warning is shown in console but spy might not catch it in test environment
|
|
340
|
+
expect(() => DyFM_Crypto.encrypt(validData, 'short')).not.toThrow();
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
describe('| Decryption Error Cases', () => {
|
|
345
|
+
it('| should throw descriptive error when decrypting undefined data', () => {
|
|
346
|
+
expect(() => DyFM_Crypto.decrypt(undefined as any, validKey)).toThrow();
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('| should throw descriptive error when decrypting null data', () => {
|
|
350
|
+
expect(() => DyFM_Crypto.decrypt(null as any, validKey)).toThrow();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('| should throw descriptive error when decrypting non-string data', () => {
|
|
354
|
+
expect(() => DyFM_Crypto.decrypt(123 as any, validKey)).toThrow();
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('| should throw descriptive error when decrypting empty string', () => {
|
|
358
|
+
expect(() => DyFM_Crypto.decrypt('', validKey)).toThrow();
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('| should throw descriptive error when decrypting too short data', () => {
|
|
362
|
+
expect(() => DyFM_Crypto.decrypt('short', validKey)).toThrow();
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('| should throw descriptive error when decrypting invalid format', () => {
|
|
366
|
+
expect(() => DyFM_Crypto.decrypt('invalid!@#format', validKey)).toThrow();
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('| should throw descriptive error when key is missing for decryption', () => {
|
|
370
|
+
const encrypted = DyFM_Crypto.encrypt(validData, validKey);
|
|
371
|
+
expect(() => DyFM_Crypto.decrypt(encrypted, undefined as any)).toThrow();
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('| should throw descriptive error when key is wrong type for decryption', () => {
|
|
375
|
+
const encrypted = DyFM_Crypto.encrypt(validData, validKey);
|
|
376
|
+
expect(() => DyFM_Crypto.decrypt(encrypted, 123 as any)).toThrow();
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('| should throw descriptive error when key is empty for decryption', () => {
|
|
380
|
+
const encrypted = DyFM_Crypto.encrypt(validData, validKey);
|
|
381
|
+
expect(() => DyFM_Crypto.decrypt(encrypted, '')).toThrow();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('| should handle decryption with proper key validation', () => {
|
|
385
|
+
// Test that decryption works with valid key
|
|
386
|
+
const encrypted = DyFM_Crypto.encrypt(validData, validKey);
|
|
387
|
+
expect(() => DyFM_Crypto.decrypt(encrypted, validKey)).not.toThrow();
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
describe('| Decryption Failure Scenarios', () => {
|
|
392
|
+
it('| should throw descriptive error when decrypting with wrong key', () => {
|
|
393
|
+
const encrypted = DyFM_Crypto.encrypt(validData, validKey);
|
|
394
|
+
expect(() => DyFM_Crypto.decrypt(encrypted, 'wrong-key-123')).toThrow();
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it('| should throw descriptive error when decrypting corrupted data', () => {
|
|
398
|
+
const encrypted = DyFM_Crypto.encrypt(validData, validKey);
|
|
399
|
+
const corrupted = encrypted.substring(0, encrypted.length - 10); // Remove last 10 characters
|
|
400
|
+
expect(() => DyFM_Crypto.decrypt(corrupted, validKey)).toThrow();
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('| should throw descriptive error when decrypting invalid base64', () => {
|
|
404
|
+
expect(() => DyFM_Crypto.decrypt('invalid-base64!@#', validKey)).toThrow();
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('| should throw descriptive error when decrypting completely wrong data', () => {
|
|
408
|
+
expect(() => DyFM_Crypto.decrypt('this-is-not-encrypted-data', validKey)).toThrow();
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
describe('| Edge Case Validations', () => {
|
|
413
|
+
it('| should allow valid data with minimum key length', () => {
|
|
414
|
+
const shortKey = '12345678'; // 8 characters minimum
|
|
415
|
+
const encrypted = DyFM_Crypto.encrypt(validData, shortKey);
|
|
416
|
+
const decrypted = DyFM_Crypto.decrypt(encrypted, shortKey);
|
|
417
|
+
expect(decrypted).toEqual(validData);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it('| should allow valid data with whitespace in key', () => {
|
|
421
|
+
const keyWithSpaces = ' valid-key-123 ';
|
|
422
|
+
const encrypted = DyFM_Crypto.encrypt(validData, keyWithSpaces);
|
|
423
|
+
const decrypted = DyFM_Crypto.decrypt(encrypted, keyWithSpaces);
|
|
424
|
+
expect(decrypted).toEqual(validData);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('| should handle special characters in data', () => {
|
|
428
|
+
const specialData = {
|
|
429
|
+
special: '!@#$%^&*()_+-=[]{}|;:,.<>?',
|
|
430
|
+
unicode: '世界你好',
|
|
431
|
+
emoji: '🚀💻🔐'
|
|
432
|
+
};
|
|
433
|
+
const encrypted = DyFM_Crypto.encrypt(specialData, validKey);
|
|
434
|
+
const decrypted = DyFM_Crypto.decrypt(encrypted, validKey);
|
|
435
|
+
expect(decrypted).toEqual(specialData);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it('| should handle very long data', () => {
|
|
439
|
+
const longData = {
|
|
440
|
+
text: 'A'.repeat(10000),
|
|
441
|
+
array: Array(1000).fill(0).map((_, i) => i),
|
|
442
|
+
nested: {
|
|
443
|
+
level1: { level2: { level3: { data: 'deep nested data' } } }
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
const encrypted = DyFM_Crypto.encrypt(longData, validKey);
|
|
447
|
+
const decrypted = DyFM_Crypto.decrypt(encrypted, validKey);
|
|
448
|
+
expect(decrypted).toEqual(longData);
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
describe('| Error Code Validation', () => {
|
|
453
|
+
it('| should have specific error codes for different failure types', () => {
|
|
454
|
+
try {
|
|
455
|
+
DyFM_Crypto.encrypt(undefined, validKey);
|
|
456
|
+
fail('Should have thrown an error');
|
|
457
|
+
} catch (error: any) {
|
|
458
|
+
expect(error._errorCode).toBe('DyFM-CRY-DATA-UNDEFINED');
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
try {
|
|
462
|
+
DyFM_Crypto.encrypt(validData, '');
|
|
463
|
+
fail('Should have thrown an error');
|
|
464
|
+
} catch (error: any) {
|
|
465
|
+
expect(error._errorCode).toBe('DyFM-CRY-KEY-MISSING');
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
try {
|
|
469
|
+
DyFM_Crypto.decrypt('', validKey);
|
|
470
|
+
fail('Should have thrown an error');
|
|
471
|
+
} catch (error: any) {
|
|
472
|
+
expect(error._errorCode).toBe('DyFM-CRY-ENCRYPTED-EMPTY');
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
describe('| specific byte count and corruption scenarios', () => {
|
|
479
|
+
const validKey = 'valid-test-key-123';
|
|
480
|
+
const validData = { id: 1, name: 'test' };
|
|
481
|
+
|
|
482
|
+
it('| should throw DyFM-CRY-DATA-CORRUPTED error with correct message for 18 bytes', () => {
|
|
483
|
+
// Create a base64 string that parses to exactly 18 bytes
|
|
484
|
+
// 18 bytes = 24 base64 characters (18 * 4/3 = 24)
|
|
485
|
+
const exactly18Bytes = 'ABCDEFGHIJKLMNOPQRSTUVWX'; // 24 chars = 18 bytes
|
|
486
|
+
|
|
487
|
+
try {
|
|
488
|
+
DyFM_Crypto.decrypt(exactly18Bytes, validKey);
|
|
489
|
+
fail('Should have thrown an error');
|
|
490
|
+
} catch (error: any) {
|
|
491
|
+
expect(error._errorCode).toBe('DyFM-CRY-DATA-CORRUPTED');
|
|
492
|
+
// Pre-parse validation catches this, so check for base64 character count message
|
|
493
|
+
expect(error._message || error.message || '').toContain('too short');
|
|
494
|
+
expect(error._message || error.message || '').toContain('characters');
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('| should throw error for 16 bytes (too short)', () => {
|
|
499
|
+
// 16 bytes = ~22 base64 characters (but we need exact 16 bytes = 21.33 chars, round to 22)
|
|
500
|
+
const exactly16Bytes = 'ABCDEFGHIJKLMNOPQRSTUV'; // 22 chars ≈ 16 bytes
|
|
501
|
+
|
|
502
|
+
try {
|
|
503
|
+
DyFM_Crypto.decrypt(exactly16Bytes, validKey);
|
|
504
|
+
fail('Should have thrown an error');
|
|
505
|
+
} catch (error: any) {
|
|
506
|
+
expect(error._errorCode).toBe('DyFM-CRY-DATA-CORRUPTED');
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('| should throw error for 24 bytes (too short)', () => {
|
|
511
|
+
// 24 bytes = 32 base64 characters
|
|
512
|
+
const exactly24Bytes = 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCD'; // 32 chars = 24 bytes
|
|
513
|
+
|
|
514
|
+
try {
|
|
515
|
+
DyFM_Crypto.decrypt(exactly24Bytes, validKey);
|
|
516
|
+
fail('Should have thrown an error');
|
|
517
|
+
} catch (error: any) {
|
|
518
|
+
expect(error._errorCode).toBe('DyFM-CRY-DATA-CORRUPTED');
|
|
519
|
+
// Pre-parse validation catches this, so check for base64 character count message
|
|
520
|
+
expect(error._message || error.message || '').toContain('too short');
|
|
521
|
+
expect(error._message || error.message || '').toContain('characters');
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('| should throw error for 32 bytes (too short)', () => {
|
|
526
|
+
// 32 bytes = 43 base64 characters (32 * 4/3 = 42.67, round to 43)
|
|
527
|
+
const exactly32Bytes = 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOP'; // 43 chars ≈ 32 bytes
|
|
528
|
+
|
|
529
|
+
try {
|
|
530
|
+
DyFM_Crypto.decrypt(exactly32Bytes, validKey);
|
|
531
|
+
fail('Should have thrown an error');
|
|
532
|
+
} catch (error: any) {
|
|
533
|
+
expect(error._errorCode).toBe('DyFM-CRY-DATA-CORRUPTED');
|
|
534
|
+
// Pre-parse validation catches this, so check for base64 character count message
|
|
535
|
+
expect(error._message || error.message || '').toContain('too short');
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it('| should throw error for 40 bytes (too short)', () => {
|
|
540
|
+
// 40 bytes = 54 base64 characters (40 * 4/3 = 53.33, round to 54)
|
|
541
|
+
const exactly40Bytes = 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZAB'; // 54 chars ≈ 40 bytes
|
|
542
|
+
|
|
543
|
+
try {
|
|
544
|
+
DyFM_Crypto.decrypt(exactly40Bytes, validKey);
|
|
545
|
+
fail('Should have thrown an error');
|
|
546
|
+
} catch (error: any) {
|
|
547
|
+
expect(error._errorCode).toBe('DyFM-CRY-DATA-CORRUPTED');
|
|
548
|
+
// Pre-parse validation catches this, so check for base64 character count message
|
|
549
|
+
expect(error._message || error.message || '').toContain('too short');
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it('| should validate error message includes correct expected byte count (48)', () => {
|
|
554
|
+
const encrypted = DyFM_Crypto.encrypt(validData, validKey);
|
|
555
|
+
// Truncate to make it less than 48 bytes
|
|
556
|
+
const truncated = encrypted.substring(0, 20); // Very short
|
|
557
|
+
|
|
558
|
+
try {
|
|
559
|
+
DyFM_Crypto.decrypt(truncated, validKey);
|
|
560
|
+
fail('Should have thrown an error');
|
|
561
|
+
} catch (error: any) {
|
|
562
|
+
expect(error._errorCode).toBe('DyFM-CRY-DATA-CORRUPTED');
|
|
563
|
+
// Pre-parse validation catches this, so check for base64 character count or byte count message
|
|
564
|
+
const errorMsg = error._message || error.message || '';
|
|
565
|
+
expect(errorMsg).toMatch(/Expected at least|too short|48|bytes|characters/);
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
it('| should validate error message includes correct received byte count', () => {
|
|
570
|
+
// Create a base64 string that parses to exactly 18 bytes
|
|
571
|
+
const exactly18Bytes = 'ABCDEFGHIJKLMNOPQRSTUVWX'; // 24 chars = 18 bytes
|
|
572
|
+
|
|
573
|
+
try {
|
|
574
|
+
DyFM_Crypto.decrypt(exactly18Bytes, validKey);
|
|
575
|
+
fail('Should have thrown an error');
|
|
576
|
+
} catch (error: any) {
|
|
577
|
+
expect(error._errorCode).toBe('DyFM-CRY-DATA-CORRUPTED');
|
|
578
|
+
// Pre-parse validation catches this, so check for character count or byte count in message
|
|
579
|
+
const errorMsg = error._message || error.message || '';
|
|
580
|
+
expect(errorMsg).toMatch(/received|characters|bytes|original/);
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('| should handle scenarios where base64 string length does not match parsed byte count', () => {
|
|
585
|
+
// Create a base64 string with padding that affects byte count
|
|
586
|
+
const encrypted = DyFM_Crypto.encrypt(validData, validKey);
|
|
587
|
+
// Add padding characters that shouldn't be there
|
|
588
|
+
const withExtraPadding = encrypted + '==';
|
|
589
|
+
|
|
590
|
+
try {
|
|
591
|
+
DyFM_Crypto.decrypt(withExtraPadding, validKey);
|
|
592
|
+
fail('Should have thrown an error');
|
|
593
|
+
} catch (error: any) {
|
|
594
|
+
// Should throw some kind of error (either corrupted or wrong key)
|
|
595
|
+
expect(() => {
|
|
596
|
+
throw error;
|
|
597
|
+
}).toThrow();
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
it('| should throw error for 64 bytes when truncated from valid encrypted data', () => {
|
|
602
|
+
const encrypted = DyFM_Crypto.encrypt(validData, validKey);
|
|
603
|
+
// Truncate encrypted data to simulate corruption
|
|
604
|
+
// Use a shorter truncation that will definitely fail validation
|
|
605
|
+
// 50 characters is less than 64 (minimum expected), so it will fail pre-parse validation
|
|
606
|
+
const truncated = encrypted.substring(0, 50);
|
|
607
|
+
|
|
608
|
+
try {
|
|
609
|
+
DyFM_Crypto.decrypt(truncated, validKey);
|
|
610
|
+
fail('Should have thrown an error');
|
|
611
|
+
} catch (error: any) {
|
|
612
|
+
// Should throw an error - DATA-CORRUPTED from pre-parse validation
|
|
613
|
+
expect(error._errorCode).toBe('DyFM-CRY-DATA-CORRUPTED');
|
|
614
|
+
expect(() => {
|
|
615
|
+
throw error;
|
|
616
|
+
}).toThrow();
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it('| should validate error code for various byte count scenarios', () => {
|
|
621
|
+
const byteCounts = [
|
|
622
|
+
{ bytes: 16, base64Length: 22 },
|
|
623
|
+
{ bytes: 18, base64Length: 24 },
|
|
624
|
+
{ bytes: 24, base64Length: 32 },
|
|
625
|
+
{ bytes: 32, base64Length: 43 },
|
|
626
|
+
{ bytes: 40, base64Length: 54 }
|
|
627
|
+
];
|
|
628
|
+
|
|
629
|
+
byteCounts.forEach(({ base64Length }) => {
|
|
630
|
+
// Create a base64 string of the specified length
|
|
631
|
+
const shortBase64 = 'A'.repeat(base64Length);
|
|
632
|
+
|
|
633
|
+
try {
|
|
634
|
+
DyFM_Crypto.decrypt(shortBase64, validKey);
|
|
635
|
+
fail(`Should have thrown an error for ${base64Length} character base64 string`);
|
|
636
|
+
} catch (error: any) {
|
|
637
|
+
// All should throw DATA-CORRUPTED or similar error
|
|
638
|
+
expect(error._errorCode).toBeDefined();
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
});
|
|
643
|
+
});
|