@adaas/a-utils 0.1.11 → 0.1.13
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 +87 -32
- package/dist/index.d.mts +33 -6
- package/dist/index.d.ts +33 -6
- package/dist/index.js +66 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +66 -12
- package/dist/index.mjs.map +1 -1
- package/examples/command-examples.ts +268 -0
- package/package.json +1 -1
- package/src/lib/A-Command/A-Command.constants.ts +4 -0
- package/src/lib/A-Command/A-Command.entity.ts +74 -15
- package/src/lib/A-Command/A-Command.error.ts +2 -0
- package/src/lib/A-Command/A-Command.types.ts +6 -1
- package/src/lib/A-Command/README.md +645 -0
- package/src/lib/A-Memory/A-Memory.context.ts +15 -0
- package/tests/A-Command.test.ts +447 -2
- package/tests/A-Memory.test.ts +189 -0
package/tests/A-Command.test.ts
CHANGED
|
@@ -16,7 +16,7 @@ describe('A-Command tests', () => {
|
|
|
16
16
|
expect(command.code).toBe('a-command');
|
|
17
17
|
expect(command.id).toBeDefined();
|
|
18
18
|
expect(command.aseid).toBeDefined();
|
|
19
|
-
expect(command.status).toBe(A_CONSTANTS__A_Command_Status.
|
|
19
|
+
expect(command.status).toBe(A_CONSTANTS__A_Command_Status.CREATED);
|
|
20
20
|
expect(command.scope).toBeInstanceOf(A_Scope);
|
|
21
21
|
expect(command.scope.resolve(A_Memory)).toBeInstanceOf(A_Memory);
|
|
22
22
|
});
|
|
@@ -30,6 +30,30 @@ describe('A-Command tests', () => {
|
|
|
30
30
|
expect(command.startedAt).toBeInstanceOf(Date);
|
|
31
31
|
expect(command.endedAt).toBeInstanceOf(Date);
|
|
32
32
|
});
|
|
33
|
+
it('Should Allow to create a command with custom generic types', async () => {
|
|
34
|
+
type LifecycleEvents = 'A_CUSTOM_EVENT_1' | 'A_CUSTOM_EVENT_2';
|
|
35
|
+
|
|
36
|
+
class MyCommand extends A_Command<
|
|
37
|
+
{ foo: string },
|
|
38
|
+
{ bar: string },
|
|
39
|
+
LifecycleEvents
|
|
40
|
+
> { }
|
|
41
|
+
|
|
42
|
+
const command = new MyCommand({ foo: 'baz' });
|
|
43
|
+
|
|
44
|
+
A_Context.root.register(command);
|
|
45
|
+
|
|
46
|
+
command.emit('A_CUSTOM_EVENT_1');
|
|
47
|
+
command.emit('compile');
|
|
48
|
+
|
|
49
|
+
expect(command).toBeInstanceOf(A_Command);
|
|
50
|
+
expect(command.code).toBe('my-command');
|
|
51
|
+
expect(command.id).toBeDefined();
|
|
52
|
+
expect(command.aseid).toBeDefined();
|
|
53
|
+
expect(command.status).toBe(A_CONSTANTS__A_Command_Status.CREATED);
|
|
54
|
+
expect(command.scope).toBeInstanceOf(A_Scope);
|
|
55
|
+
expect(command.scope.resolve(A_Memory)).toBeInstanceOf(A_Memory);
|
|
56
|
+
});
|
|
33
57
|
it('Should allow to serialize and deserialize a command', async () => {
|
|
34
58
|
const command = new A_Command({});
|
|
35
59
|
A_Context.root.register(command);
|
|
@@ -53,7 +77,7 @@ describe('A-Command tests', () => {
|
|
|
53
77
|
expect(deserializedCommand.startedAt?.toISOString()).toBe(command.startedAt?.toISOString());
|
|
54
78
|
expect(deserializedCommand.duration).toBe(command.duration);
|
|
55
79
|
});
|
|
56
|
-
|
|
80
|
+
|
|
57
81
|
it('Should allow to execute a command with custom logic', async () => {
|
|
58
82
|
|
|
59
83
|
// 1) create a scope
|
|
@@ -131,4 +155,425 @@ describe('A-Command tests', () => {
|
|
|
131
155
|
expect(command.errors?.size).toBe(1);
|
|
132
156
|
expect(Array.from(command.errors?.values() || [])[0].message).toBe('Test error');
|
|
133
157
|
});
|
|
158
|
+
|
|
159
|
+
describe('Command Lifecycle Tests', () => {
|
|
160
|
+
beforeEach(() => {
|
|
161
|
+
A_Context.reset();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('Should follow correct lifecycle sequence during execution', async () => {
|
|
165
|
+
const lifecycleOrder: string[] = [];
|
|
166
|
+
|
|
167
|
+
class TestCommand extends A_Command<{ input: string }, { output: string }> {}
|
|
168
|
+
|
|
169
|
+
const command = new TestCommand({ input: 'test' });
|
|
170
|
+
A_Context.root.register(command);
|
|
171
|
+
|
|
172
|
+
// Track lifecycle events
|
|
173
|
+
command.on('init', () => lifecycleOrder.push('init'));
|
|
174
|
+
command.on('compile', () => lifecycleOrder.push('compile'));
|
|
175
|
+
command.on('execute', () => lifecycleOrder.push('execute'));
|
|
176
|
+
command.on('complete', () => lifecycleOrder.push('complete'));
|
|
177
|
+
command.on('fail', () => lifecycleOrder.push('fail'));
|
|
178
|
+
|
|
179
|
+
await command.execute();
|
|
180
|
+
|
|
181
|
+
expect(lifecycleOrder).toEqual(['init', 'compile', 'execute', 'complete']);
|
|
182
|
+
expect(command.status).toBe(A_CONSTANTS__A_Command_Status.COMPLETED);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('Should track status changes through lifecycle', async () => {
|
|
186
|
+
const statusChanges: A_CONSTANTS__A_Command_Status[] = [];
|
|
187
|
+
|
|
188
|
+
class TestCommand extends A_Command<{ input: string }, { output: string }> {}
|
|
189
|
+
|
|
190
|
+
const command = new TestCommand({ input: 'test' });
|
|
191
|
+
A_Context.root.register(command);
|
|
192
|
+
|
|
193
|
+
// Initial status
|
|
194
|
+
expect(command.status).toBe(A_CONSTANTS__A_Command_Status.CREATED);
|
|
195
|
+
statusChanges.push(command.status);
|
|
196
|
+
|
|
197
|
+
await command.init();
|
|
198
|
+
expect(command.status).toBe(A_CONSTANTS__A_Command_Status.INITIALIZED);
|
|
199
|
+
statusChanges.push(command.status);
|
|
200
|
+
|
|
201
|
+
await command.compile();
|
|
202
|
+
expect(command.status).toBe(A_CONSTANTS__A_Command_Status.COMPILED);
|
|
203
|
+
statusChanges.push(command.status);
|
|
204
|
+
|
|
205
|
+
await command.complete();
|
|
206
|
+
expect(command.status).toBe(A_CONSTANTS__A_Command_Status.COMPLETED);
|
|
207
|
+
statusChanges.push(command.status);
|
|
208
|
+
|
|
209
|
+
expect(statusChanges).toEqual([
|
|
210
|
+
A_CONSTANTS__A_Command_Status.CREATED,
|
|
211
|
+
A_CONSTANTS__A_Command_Status.INITIALIZED,
|
|
212
|
+
A_CONSTANTS__A_Command_Status.COMPILED,
|
|
213
|
+
A_CONSTANTS__A_Command_Status.COMPLETED
|
|
214
|
+
]);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('Should handle failed lifecycle correctly', async () => {
|
|
218
|
+
A_Context.reset();
|
|
219
|
+
|
|
220
|
+
class FailingCommand extends A_Command<{ input: string }, { output: string }> {}
|
|
221
|
+
|
|
222
|
+
class FailingComponent extends A_Component {
|
|
223
|
+
@A_Feature.Extend({ scope: [FailingCommand] })
|
|
224
|
+
async [A_CONSTANTS_A_Command_Features.EXECUTE]() {
|
|
225
|
+
throw new A_Error({ title: 'Execution failed' });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
A_Context.root.register(FailingComponent);
|
|
230
|
+
|
|
231
|
+
const command = new FailingCommand({ input: 'test' });
|
|
232
|
+
A_Context.root.register(command);
|
|
233
|
+
|
|
234
|
+
const lifecycleOrder: string[] = [];
|
|
235
|
+
command.on('init', () => lifecycleOrder.push('init'));
|
|
236
|
+
command.on('compile', () => lifecycleOrder.push('compile'));
|
|
237
|
+
command.on('execute', () => lifecycleOrder.push('execute'));
|
|
238
|
+
command.on('complete', () => lifecycleOrder.push('complete'));
|
|
239
|
+
command.on('fail', () => lifecycleOrder.push('fail'));
|
|
240
|
+
|
|
241
|
+
await command.execute();
|
|
242
|
+
|
|
243
|
+
expect(lifecycleOrder).toEqual(['init', 'compile', 'execute', 'fail']);
|
|
244
|
+
expect(command.status).toBe(A_CONSTANTS__A_Command_Status.FAILED);
|
|
245
|
+
expect(command.isFailed).toBe(true);
|
|
246
|
+
expect(command.isCompleted).toBe(false);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('Should track execution timing correctly', async () => {
|
|
250
|
+
const command = new A_Command({});
|
|
251
|
+
A_Context.root.register(command);
|
|
252
|
+
|
|
253
|
+
expect(command.startedAt).toBeUndefined();
|
|
254
|
+
expect(command.endedAt).toBeUndefined();
|
|
255
|
+
expect(command.duration).toBeUndefined();
|
|
256
|
+
|
|
257
|
+
const startTime = Date.now();
|
|
258
|
+
await command.execute();
|
|
259
|
+
const endTime = Date.now();
|
|
260
|
+
|
|
261
|
+
expect(command.startedAt).toBeInstanceOf(Date);
|
|
262
|
+
expect(command.endedAt).toBeInstanceOf(Date);
|
|
263
|
+
expect(command.duration).toBeGreaterThanOrEqual(0);
|
|
264
|
+
expect(command.startedAt!.getTime()).toBeGreaterThanOrEqual(startTime);
|
|
265
|
+
expect(command.endedAt!.getTime()).toBeLessThanOrEqual(endTime);
|
|
266
|
+
expect(command.duration).toBe(command.endedAt!.getTime() - command.startedAt!.getTime());
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('Command Subscribers/Event Listeners Tests', () => {
|
|
271
|
+
beforeEach(() => {
|
|
272
|
+
A_Context.reset();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('Should allow multiple listeners for the same event', async () => {
|
|
276
|
+
const command = new A_Command({});
|
|
277
|
+
A_Context.root.register(command);
|
|
278
|
+
|
|
279
|
+
const listener1Calls: number[] = [];
|
|
280
|
+
const listener2Calls: number[] = [];
|
|
281
|
+
|
|
282
|
+
command.on('init', () => listener1Calls.push(1));
|
|
283
|
+
command.on('init', () => listener2Calls.push(2));
|
|
284
|
+
|
|
285
|
+
await command.init();
|
|
286
|
+
|
|
287
|
+
expect(listener1Calls).toEqual([1]);
|
|
288
|
+
expect(listener2Calls).toEqual([2]);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('Should support custom lifecycle events', async () => {
|
|
292
|
+
type CustomEvents = 'custom-event-1' | 'custom-event-2';
|
|
293
|
+
|
|
294
|
+
class CustomCommand extends A_Command<{}, {}, CustomEvents> {}
|
|
295
|
+
|
|
296
|
+
const command = new CustomCommand({});
|
|
297
|
+
A_Context.root.register(command);
|
|
298
|
+
|
|
299
|
+
const customEvent1Calls: number[] = [];
|
|
300
|
+
const customEvent2Calls: number[] = [];
|
|
301
|
+
|
|
302
|
+
command.on('custom-event-1', () => customEvent1Calls.push(1));
|
|
303
|
+
command.on('custom-event-2', () => customEvent2Calls.push(2));
|
|
304
|
+
|
|
305
|
+
command.emit('custom-event-1');
|
|
306
|
+
command.emit('custom-event-2');
|
|
307
|
+
command.emit('custom-event-1');
|
|
308
|
+
|
|
309
|
+
expect(customEvent1Calls).toEqual([1, 1]);
|
|
310
|
+
expect(customEvent2Calls).toEqual([2]);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('Should allow removing event listeners', async () => {
|
|
314
|
+
const command = new A_Command({});
|
|
315
|
+
A_Context.root.register(command);
|
|
316
|
+
|
|
317
|
+
const listenerCalls: number[] = [];
|
|
318
|
+
const listener = () => listenerCalls.push(1);
|
|
319
|
+
|
|
320
|
+
command.on('init', listener);
|
|
321
|
+
await command.init();
|
|
322
|
+
expect(listenerCalls).toEqual([1]);
|
|
323
|
+
|
|
324
|
+
// Remove listener and verify it's not called again
|
|
325
|
+
command.off('init', listener);
|
|
326
|
+
|
|
327
|
+
// Reset to call init again
|
|
328
|
+
(command as any)._status = A_CONSTANTS__A_Command_Status.CREATED;
|
|
329
|
+
await command.init();
|
|
330
|
+
expect(listenerCalls).toEqual([1]); // Should still be 1, not 2
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('Should pass command instance to event listeners', async () => {
|
|
334
|
+
const command = new A_Command({ testParam: 'value' });
|
|
335
|
+
A_Context.root.register(command);
|
|
336
|
+
|
|
337
|
+
let receivedCommand: A_Command<any, any, any> | undefined = undefined;
|
|
338
|
+
|
|
339
|
+
command.on('init', (cmd) => {
|
|
340
|
+
receivedCommand = cmd;
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
await command.init();
|
|
344
|
+
|
|
345
|
+
expect(receivedCommand).toBe(command);
|
|
346
|
+
expect((receivedCommand as any)?.params).toEqual({ testParam: 'value' });
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('Should propagate listener errors during event emission', async () => {
|
|
350
|
+
const command = new A_Command({});
|
|
351
|
+
A_Context.root.register(command);
|
|
352
|
+
|
|
353
|
+
const successfulCalls: number[] = [];
|
|
354
|
+
|
|
355
|
+
command.on('init', () => {
|
|
356
|
+
throw new Error('Listener error');
|
|
357
|
+
});
|
|
358
|
+
command.on('init', () => successfulCalls.push(1));
|
|
359
|
+
|
|
360
|
+
// Listener errors are propagated and will cause the command to fail
|
|
361
|
+
await expect(command.init()).rejects.toThrow('Listener error');
|
|
362
|
+
// The second listener may not be called due to the error
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
describe('Parameter Serialization and Transmission Tests', () => {
|
|
367
|
+
beforeEach(() => {
|
|
368
|
+
A_Context.reset();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('Should preserve complex parameter types during serialization', async () => {
|
|
372
|
+
interface ComplexParams {
|
|
373
|
+
stringParam: string;
|
|
374
|
+
numberParam: number;
|
|
375
|
+
booleanParam: boolean;
|
|
376
|
+
objectParam: {
|
|
377
|
+
nested: string;
|
|
378
|
+
array: number[];
|
|
379
|
+
};
|
|
380
|
+
arrayParam: string[];
|
|
381
|
+
dateParam: string; // ISO string representation
|
|
382
|
+
nullParam: null;
|
|
383
|
+
undefinedParam?: undefined;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const complexParams: ComplexParams = {
|
|
387
|
+
stringParam: 'test string',
|
|
388
|
+
numberParam: 42,
|
|
389
|
+
booleanParam: true,
|
|
390
|
+
objectParam: {
|
|
391
|
+
nested: 'nested value',
|
|
392
|
+
array: [1, 2, 3]
|
|
393
|
+
},
|
|
394
|
+
arrayParam: ['a', 'b', 'c'],
|
|
395
|
+
dateParam: new Date('2023-01-01').toISOString(),
|
|
396
|
+
nullParam: null
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
class ComplexCommand extends A_Command<ComplexParams, { result: string }> {}
|
|
400
|
+
|
|
401
|
+
const command = new ComplexCommand(complexParams);
|
|
402
|
+
A_Context.root.register(command);
|
|
403
|
+
|
|
404
|
+
await command.execute();
|
|
405
|
+
|
|
406
|
+
const serialized = command.toJSON();
|
|
407
|
+
expect(serialized.params).toEqual(complexParams);
|
|
408
|
+
|
|
409
|
+
// Test deserialization
|
|
410
|
+
const deserializedCommand = new ComplexCommand(serialized);
|
|
411
|
+
expect(deserializedCommand.params).toEqual(complexParams);
|
|
412
|
+
expect(deserializedCommand.params.objectParam.nested).toBe('nested value');
|
|
413
|
+
expect(deserializedCommand.params.arrayParam).toEqual(['a', 'b', 'c']);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it('Should handle result serialization correctly', async () => {
|
|
417
|
+
A_Context.reset();
|
|
418
|
+
|
|
419
|
+
interface TestResult {
|
|
420
|
+
processedData: string;
|
|
421
|
+
count: number;
|
|
422
|
+
metadata: {
|
|
423
|
+
timestamp: string;
|
|
424
|
+
version: number;
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
class ResultCommand extends A_Command<{ input: string }, TestResult> {}
|
|
429
|
+
|
|
430
|
+
class ResultProcessor extends A_Component {
|
|
431
|
+
@A_Feature.Extend({ scope: [ResultCommand] })
|
|
432
|
+
async [A_CONSTANTS_A_Command_Features.EXECUTE](
|
|
433
|
+
@A_Inject(A_Memory) memory: A_Memory<TestResult>
|
|
434
|
+
) {
|
|
435
|
+
memory.set('processedData', 'processed-input');
|
|
436
|
+
memory.set('count', 100);
|
|
437
|
+
memory.set('metadata', {
|
|
438
|
+
timestamp: new Date().toISOString(),
|
|
439
|
+
version: 1
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
A_Context.root.register(ResultProcessor);
|
|
445
|
+
|
|
446
|
+
const command = new ResultCommand({ input: 'test-input' });
|
|
447
|
+
A_Context.root.register(command);
|
|
448
|
+
|
|
449
|
+
await command.execute();
|
|
450
|
+
|
|
451
|
+
const serialized = command.toJSON();
|
|
452
|
+
expect(serialized.result).toBeDefined();
|
|
453
|
+
expect(serialized.result?.processedData).toBe('processed-input');
|
|
454
|
+
expect(serialized.result?.count).toBe(100);
|
|
455
|
+
expect(serialized.result?.metadata).toBeDefined();
|
|
456
|
+
expect(serialized.result?.metadata.version).toBe(1);
|
|
457
|
+
|
|
458
|
+
// Test deserialization - result is restored to memory and accessible through get method
|
|
459
|
+
const deserializedCommand = new ResultCommand(serialized);
|
|
460
|
+
const deserializedMemory = deserializedCommand.scope.resolve(A_Memory);
|
|
461
|
+
expect(deserializedMemory.get('processedData')).toBe('processed-input');
|
|
462
|
+
expect(deserializedMemory.get('count')).toBe(100);
|
|
463
|
+
expect(deserializedMemory.get('metadata')).toEqual(serialized.result?.metadata);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it('Should handle error serialization correctly', async () => {
|
|
467
|
+
A_Context.reset();
|
|
468
|
+
|
|
469
|
+
class ErrorCommand extends A_Command<{ input: string }, { output: string }> {}
|
|
470
|
+
|
|
471
|
+
class ErrorComponent extends A_Component {
|
|
472
|
+
@A_Feature.Extend({ scope: [ErrorCommand] })
|
|
473
|
+
async [A_CONSTANTS_A_Command_Features.EXECUTE](
|
|
474
|
+
@A_Inject(A_Memory) memory: A_Memory<{ output: string }>
|
|
475
|
+
) {
|
|
476
|
+
memory.error(new A_Error({
|
|
477
|
+
title: 'First error',
|
|
478
|
+
message: 'First error message'
|
|
479
|
+
}));
|
|
480
|
+
memory.error(new A_Error({
|
|
481
|
+
title: 'Second error',
|
|
482
|
+
message: 'Second error message'
|
|
483
|
+
}));
|
|
484
|
+
throw new A_Error({ title: 'Thrown error' });
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
A_Context.root.register(ErrorComponent);
|
|
489
|
+
|
|
490
|
+
const command = new ErrorCommand({ input: 'test' });
|
|
491
|
+
A_Context.root.register(command);
|
|
492
|
+
|
|
493
|
+
await command.execute();
|
|
494
|
+
|
|
495
|
+
expect(command.isFailed).toBe(true);
|
|
496
|
+
expect(command.errors?.size).toBe(2);
|
|
497
|
+
|
|
498
|
+
const serialized = command.toJSON();
|
|
499
|
+
expect(serialized.errors).toBeDefined();
|
|
500
|
+
expect(serialized.errors?.length).toBe(2);
|
|
501
|
+
expect(serialized.errors?.[0].title).toBe('First error');
|
|
502
|
+
expect(serialized.errors?.[1].title).toBe('Second error');
|
|
503
|
+
|
|
504
|
+
// Test deserialization - errors are restored to memory
|
|
505
|
+
const deserializedCommand = new ErrorCommand(serialized);
|
|
506
|
+
const deserializedMemory = deserializedCommand.scope.resolve(A_Memory);
|
|
507
|
+
expect(deserializedMemory.Errors?.size).toBe(2);
|
|
508
|
+
const errorArray = Array.from(deserializedMemory.Errors?.values() || []);
|
|
509
|
+
expect(errorArray[0].title).toBe('First error');
|
|
510
|
+
expect(errorArray[1].title).toBe('Second error');
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it('Should maintain parameter integrity across command transmission', async () => {
|
|
514
|
+
// Simulate command transmission across network/storage
|
|
515
|
+
const originalParams = {
|
|
516
|
+
userId: '12345',
|
|
517
|
+
action: 'update',
|
|
518
|
+
data: {
|
|
519
|
+
email: 'test@example.com',
|
|
520
|
+
preferences: {
|
|
521
|
+
theme: 'dark',
|
|
522
|
+
notifications: true
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
timestamp: new Date().toISOString()
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
class TransmissionCommand extends A_Command<typeof originalParams, { success: boolean }> {}
|
|
529
|
+
|
|
530
|
+
// Step 1: Create and execute original command
|
|
531
|
+
const originalCommand = new TransmissionCommand(originalParams);
|
|
532
|
+
A_Context.root.register(originalCommand);
|
|
533
|
+
await originalCommand.execute();
|
|
534
|
+
|
|
535
|
+
// Step 2: Serialize for transmission
|
|
536
|
+
const serializedForTransmission = JSON.stringify(originalCommand.toJSON());
|
|
537
|
+
|
|
538
|
+
// Step 3: Deserialize from transmission
|
|
539
|
+
const deserializedData = JSON.parse(serializedForTransmission);
|
|
540
|
+
const restoredCommand = new TransmissionCommand(deserializedData);
|
|
541
|
+
|
|
542
|
+
// Step 4: Verify parameter integrity
|
|
543
|
+
expect(restoredCommand.params).toEqual(originalParams);
|
|
544
|
+
expect(restoredCommand.params.data.email).toBe('test@example.com');
|
|
545
|
+
expect(restoredCommand.params.data.preferences.theme).toBe('dark');
|
|
546
|
+
expect(restoredCommand.aseid.toString()).toBe(originalCommand.aseid.toString());
|
|
547
|
+
expect(restoredCommand.code).toBe(originalCommand.code);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it('Should handle empty and edge case parameters', async () => {
|
|
551
|
+
const edgeCaseParams = {
|
|
552
|
+
emptyString: '',
|
|
553
|
+
emptyArray: [],
|
|
554
|
+
emptyObject: {},
|
|
555
|
+
zeroNumber: 0,
|
|
556
|
+
falseBoolean: false,
|
|
557
|
+
nullValue: null
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
class EdgeCaseCommand extends A_Command<typeof edgeCaseParams, {}> {}
|
|
561
|
+
|
|
562
|
+
const command = new EdgeCaseCommand(edgeCaseParams);
|
|
563
|
+
A_Context.root.register(command);
|
|
564
|
+
await command.execute();
|
|
565
|
+
|
|
566
|
+
const serialized = command.toJSON();
|
|
567
|
+
expect(serialized.params).toEqual(edgeCaseParams);
|
|
568
|
+
|
|
569
|
+
const deserializedCommand = new EdgeCaseCommand(serialized);
|
|
570
|
+
expect(deserializedCommand.params).toEqual(edgeCaseParams);
|
|
571
|
+
expect(deserializedCommand.params.emptyString).toBe('');
|
|
572
|
+
expect(deserializedCommand.params.emptyArray).toEqual([]);
|
|
573
|
+
expect(deserializedCommand.params.emptyObject).toEqual({});
|
|
574
|
+
expect(deserializedCommand.params.zeroNumber).toBe(0);
|
|
575
|
+
expect(deserializedCommand.params.falseBoolean).toBe(false);
|
|
576
|
+
expect(deserializedCommand.params.nullValue).toBe(null);
|
|
577
|
+
});
|
|
578
|
+
});
|
|
134
579
|
});
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { A_Memory } from '@adaas/a-utils/lib/A-Memory/A-Memory.context';
|
|
2
|
+
import { A_Error } from '@adaas/a-concept';
|
|
3
|
+
|
|
4
|
+
jest.retryTimes(0);
|
|
5
|
+
|
|
6
|
+
describe('A-Memory tests', () => {
|
|
7
|
+
|
|
8
|
+
it('Should allow to create memory instance', () => {
|
|
9
|
+
const memory = new A_Memory();
|
|
10
|
+
|
|
11
|
+
expect(memory).toBeInstanceOf(A_Memory);
|
|
12
|
+
expect(memory.Errors).toBeUndefined();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('Should allow to create memory with initial values', () => {
|
|
16
|
+
const initialValues = {
|
|
17
|
+
key1: 'value1',
|
|
18
|
+
key2: 42,
|
|
19
|
+
key3: { nested: 'object' }
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const memory = new A_Memory(initialValues);
|
|
23
|
+
|
|
24
|
+
expect(memory.get('key1')).toBe('value1');
|
|
25
|
+
expect(memory.get('key2')).toBe(42);
|
|
26
|
+
expect(memory.get('key3')).toEqual({ nested: 'object' });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('Should allow to set and get values', async () => {
|
|
30
|
+
const memory = new A_Memory<{
|
|
31
|
+
stringValue: string;
|
|
32
|
+
numberValue: number;
|
|
33
|
+
objectValue: { prop: string };
|
|
34
|
+
}>();
|
|
35
|
+
|
|
36
|
+
await memory.set('stringValue', 'test string');
|
|
37
|
+
await memory.set('numberValue', 123);
|
|
38
|
+
await memory.set('objectValue', { prop: 'test' });
|
|
39
|
+
|
|
40
|
+
expect(memory.get('stringValue')).toBe('test string');
|
|
41
|
+
expect(memory.get('numberValue')).toBe(123);
|
|
42
|
+
expect(memory.get('objectValue')).toEqual({ prop: 'test' });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('Should return undefined for non-existent keys', () => {
|
|
46
|
+
const memory = new A_Memory<{ existingKey: string }>();
|
|
47
|
+
|
|
48
|
+
expect(memory.get('existingKey')).toBeUndefined();
|
|
49
|
+
expect(memory.get('nonExistentKey' as any)).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('Should allow to drop values', async () => {
|
|
53
|
+
const memory = new A_Memory<{ key1: string; key2: number }>();
|
|
54
|
+
|
|
55
|
+
await memory.set('key1', 'value1');
|
|
56
|
+
await memory.set('key2', 42);
|
|
57
|
+
|
|
58
|
+
expect(memory.get('key1')).toBe('value1');
|
|
59
|
+
expect(memory.get('key2')).toBe(42);
|
|
60
|
+
|
|
61
|
+
await memory.drop('key1');
|
|
62
|
+
|
|
63
|
+
expect(memory.get('key1')).toBeUndefined();
|
|
64
|
+
expect(memory.get('key2')).toBe(42);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('Should allow to clear all values', async () => {
|
|
68
|
+
const memory = new A_Memory<{ key1: string; key2: number }>();
|
|
69
|
+
|
|
70
|
+
await memory.set('key1', 'value1');
|
|
71
|
+
await memory.set('key2', 42);
|
|
72
|
+
|
|
73
|
+
expect(memory.get('key1')).toBe('value1');
|
|
74
|
+
expect(memory.get('key2')).toBe(42);
|
|
75
|
+
|
|
76
|
+
await memory.clear();
|
|
77
|
+
|
|
78
|
+
expect(memory.get('key1')).toBeUndefined();
|
|
79
|
+
expect(memory.get('key2')).toBeUndefined();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('Should handle errors correctly', async () => {
|
|
83
|
+
const memory = new A_Memory();
|
|
84
|
+
|
|
85
|
+
expect(memory.Errors).toBeUndefined();
|
|
86
|
+
|
|
87
|
+
const error1 = new A_Error({ title: 'Error 1' });
|
|
88
|
+
const error2 = new A_Error({ title: 'Error 2' });
|
|
89
|
+
|
|
90
|
+
await memory.error(error1);
|
|
91
|
+
await memory.error(error2);
|
|
92
|
+
|
|
93
|
+
expect(memory.Errors).toBeDefined();
|
|
94
|
+
expect(memory.Errors?.size).toBe(2);
|
|
95
|
+
expect(memory.Errors?.has(error1)).toBe(true);
|
|
96
|
+
expect(memory.Errors?.has(error2)).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('Should verify prerequisites correctly', async () => {
|
|
100
|
+
const memory = new A_Memory<{
|
|
101
|
+
required1: string;
|
|
102
|
+
required2: number;
|
|
103
|
+
optional?: string;
|
|
104
|
+
}>();
|
|
105
|
+
|
|
106
|
+
// No values set initially
|
|
107
|
+
expect(await memory.verifyPrerequisites(['required1', 'required2'])).toBe(false);
|
|
108
|
+
|
|
109
|
+
// Set one required value
|
|
110
|
+
await memory.set('required1', 'value1');
|
|
111
|
+
expect(await memory.verifyPrerequisites(['required1', 'required2'])).toBe(false);
|
|
112
|
+
|
|
113
|
+
// Set both required values
|
|
114
|
+
await memory.set('required2', 42);
|
|
115
|
+
expect(await memory.verifyPrerequisites(['required1', 'required2'])).toBe(true);
|
|
116
|
+
|
|
117
|
+
// Test with empty requirements
|
|
118
|
+
expect(await memory.verifyPrerequisites([])).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('Should serialize to JSON correctly', async () => {
|
|
122
|
+
const memory = new A_Memory<{
|
|
123
|
+
stringProp: string;
|
|
124
|
+
numberProp: number;
|
|
125
|
+
objectProp: { nested: string };
|
|
126
|
+
arrayProp: number[];
|
|
127
|
+
}>();
|
|
128
|
+
|
|
129
|
+
await memory.set('stringProp', 'test');
|
|
130
|
+
await memory.set('numberProp', 42);
|
|
131
|
+
await memory.set('objectProp', { nested: 'value' });
|
|
132
|
+
await memory.set('arrayProp', [1, 2, 3]);
|
|
133
|
+
|
|
134
|
+
const json = memory.toJSON();
|
|
135
|
+
|
|
136
|
+
expect(json).toEqual({
|
|
137
|
+
stringProp: 'test',
|
|
138
|
+
numberProp: 42,
|
|
139
|
+
objectProp: { nested: 'value' },
|
|
140
|
+
arrayProp: [1, 2, 3]
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('Should handle objects with toJSON method in serialization', async () => {
|
|
145
|
+
class SerializableObject {
|
|
146
|
+
constructor(private value: string) {}
|
|
147
|
+
|
|
148
|
+
toJSON() {
|
|
149
|
+
return { serialized: this.value };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const memory = new A_Memory<{
|
|
154
|
+
regular: string;
|
|
155
|
+
serializable: SerializableObject;
|
|
156
|
+
}>();
|
|
157
|
+
|
|
158
|
+
await memory.set('regular', 'normal value');
|
|
159
|
+
await memory.set('serializable', new SerializableObject('test'));
|
|
160
|
+
|
|
161
|
+
const json = memory.toJSON();
|
|
162
|
+
|
|
163
|
+
expect(json).toEqual({
|
|
164
|
+
regular: 'normal value',
|
|
165
|
+
serializable: { serialized: 'test' }
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('Should handle null and undefined values correctly', async () => {
|
|
170
|
+
const memory = new A_Memory<{
|
|
171
|
+
nullValue: null;
|
|
172
|
+
undefinedValue: undefined;
|
|
173
|
+
stringValue: string;
|
|
174
|
+
}>();
|
|
175
|
+
|
|
176
|
+
await memory.set('nullValue', null);
|
|
177
|
+
await memory.set('undefinedValue', undefined);
|
|
178
|
+
await memory.set('stringValue', 'test');
|
|
179
|
+
|
|
180
|
+
expect(memory.get('nullValue')).toBe(null);
|
|
181
|
+
expect(memory.get('undefinedValue')).toBe(undefined);
|
|
182
|
+
expect(memory.get('stringValue')).toBe('test');
|
|
183
|
+
|
|
184
|
+
const json = memory.toJSON();
|
|
185
|
+
expect(json.nullValue).toBe(null);
|
|
186
|
+
expect(json.undefinedValue).toBe(undefined);
|
|
187
|
+
expect(json.stringValue).toBe('test');
|
|
188
|
+
});
|
|
189
|
+
});
|