@adaas/a-utils 0.1.17 → 0.1.19
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/dist/index.d.mts +964 -354
- package/dist/index.d.ts +964 -354
- package/dist/index.js +1426 -714
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1426 -714
- package/dist/index.mjs.map +1 -1
- package/examples/A-Channel-examples.ts +13 -11
- package/examples/A-Command-examples-2.ts +429 -0
- package/examples/A-Command-examples.ts +487 -202
- package/examples/A-StateMachine-examples.ts +609 -0
- package/package.json +3 -2
- package/src/index.ts +1 -2
- package/src/lib/A-Channel/A-Channel.component.ts +14 -74
- package/src/lib/A-Channel/A-Channel.error.ts +5 -5
- package/src/lib/A-Channel/A-Channel.types.ts +2 -10
- package/src/lib/A-Channel/A-ChannelRequest.context.ts +25 -74
- package/src/lib/A-Command/A-Command.constants.ts +78 -23
- package/src/lib/A-Command/A-Command.entity.ts +447 -119
- package/src/lib/A-Command/A-Command.error.ts +11 -0
- package/src/lib/A-Command/A-Command.types.ts +96 -20
- package/src/lib/A-Command/A-CommandExecution.context.ts +0 -0
- package/src/lib/A-Command/README.md +164 -68
- package/src/lib/A-Config/A-Config.container.ts +2 -2
- package/src/lib/A-Config/A-Config.context.ts +19 -5
- package/src/lib/A-Config/components/ConfigReader.component.ts +1 -1
- package/src/lib/A-Logger/A-Logger.component.ts +211 -35
- package/src/lib/A-Logger/A-Logger.constants.ts +50 -10
- package/src/lib/A-Logger/A-Logger.env.ts +17 -1
- package/src/lib/A-Memory/A-Memory.component.ts +440 -0
- package/src/lib/A-Memory/A-Memory.constants.ts +49 -0
- package/src/lib/A-Memory/A-Memory.context.ts +14 -118
- package/src/lib/A-Memory/A-Memory.error.ts +21 -0
- package/src/lib/A-Memory/A-Memory.types.ts +21 -0
- package/src/lib/A-Operation/A-Operation.context.ts +58 -0
- package/src/lib/A-Operation/A-Operation.types.ts +47 -0
- package/src/lib/A-StateMachine/A-StateMachine.component.ts +258 -0
- package/src/lib/A-StateMachine/A-StateMachine.constants.ts +18 -0
- package/src/lib/A-StateMachine/A-StateMachine.error.ts +10 -0
- package/src/lib/A-StateMachine/A-StateMachine.types.ts +20 -0
- package/src/lib/A-StateMachine/A-StateMachineTransition.context.ts +41 -0
- package/src/lib/A-StateMachine/README.md +391 -0
- package/tests/A-Channel.test.ts +17 -14
- package/tests/A-Command.test.ts +548 -460
- package/tests/A-Logger.test.ts +8 -4
- package/tests/A-Memory.test.ts +151 -115
- package/tests/A-Schedule.test.ts +2 -2
- package/tests/A-StateMachine.test.ts +760 -0
package/tests/A-Command.test.ts
CHANGED
|
@@ -1,579 +1,667 @@
|
|
|
1
1
|
import { A_Command } from '@adaas/a-utils/lib/A-Command/A-Command.entity';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { A_CommandError } from '@adaas/a-utils/lib/A-Command/A-Command.error';
|
|
3
|
+
import { A_Command_Status, A_CommandFeatures } from '@adaas/a-utils/lib/A-Command/A-Command.constants';
|
|
4
|
+
import { A_TYPES__Command_Serialized } from '@adaas/a-utils/lib/A-Command/A-Command.types';
|
|
5
|
+
import { A_StateMachine } from '@adaas/a-utils/lib/A-StateMachine/A-StateMachine.component';
|
|
6
|
+
import { A_Memory } from '@adaas/a-utils/lib/A-Memory/A-Memory.component';
|
|
7
|
+
import { A_MemoryContext } from '@adaas/a-utils/lib/A-Memory/A-Memory.context';
|
|
8
|
+
import { A_Channel } from '@adaas/a-utils/lib/A-Channel/A-Channel.component';
|
|
9
|
+
import { A_ChannelRequest } from '@adaas/a-utils/lib/A-Channel/A-ChannelRequest.context';
|
|
10
|
+
import { A_Caller, A_Component, A_Concept, A_Container, A_Context, A_Error, A_Feature, A_FormatterHelper, A_Inject, A_Scope, ASEID } from '@adaas/a-concept';
|
|
5
11
|
|
|
6
12
|
jest.retryTimes(0);
|
|
7
13
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
it('Should Allow to create a command', async () => {
|
|
11
|
-
const command = new A_Command({});
|
|
12
|
-
A_Context.root.register(command);
|
|
14
|
+
// Global test execution tracking arrays
|
|
15
|
+
let testExecutionLog: string[] = [];
|
|
13
16
|
|
|
17
|
+
describe('A-Command tests', () => {
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
expect(command.aseid).toBeDefined();
|
|
19
|
-
expect(command.status).toBe(A_CONSTANTS__A_Command_Status.CREATED);
|
|
20
|
-
expect(command.scope).toBeInstanceOf(A_Scope);
|
|
21
|
-
expect(command.scope.resolve(A_Memory)).toBeInstanceOf(A_Memory);
|
|
22
|
-
});
|
|
23
|
-
it('Should allow to execute a command', async () => {
|
|
24
|
-
const command = new A_Command({});
|
|
25
|
-
A_Context.root.register(command);
|
|
26
|
-
|
|
27
|
-
await command.execute();
|
|
28
|
-
|
|
29
|
-
expect(command.status).toBe(A_CONSTANTS__A_Command_Status.COMPLETED);
|
|
30
|
-
expect(command.startedAt).toBeInstanceOf(Date);
|
|
31
|
-
expect(command.endedAt).toBeInstanceOf(Date);
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
A_Context.reset();
|
|
21
|
+
testExecutionLog = [];
|
|
32
22
|
});
|
|
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
23
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
LifecycleEvents
|
|
40
|
-
> { }
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// ======================== Basic Command Creation Tests =====================
|
|
26
|
+
// =============================================================================
|
|
41
27
|
|
|
42
|
-
|
|
28
|
+
describe('Basic Command Creation', () => {
|
|
29
|
+
interface TestCommandParams {
|
|
30
|
+
userId: string;
|
|
31
|
+
action: string;
|
|
32
|
+
}
|
|
43
33
|
|
|
44
|
-
|
|
34
|
+
interface TestCommandResult {
|
|
35
|
+
success: boolean;
|
|
36
|
+
message: string;
|
|
37
|
+
}
|
|
45
38
|
|
|
46
|
-
|
|
47
|
-
command.emit('onCompile');
|
|
39
|
+
class TestCommand extends A_Command<TestCommandParams, TestCommandResult> { }
|
|
48
40
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
});
|
|
57
|
-
it('Should allow to serialize and deserialize a command', async () => {
|
|
58
|
-
const command = new A_Command({});
|
|
59
|
-
A_Context.root.register(command);
|
|
60
|
-
|
|
61
|
-
await command.execute();
|
|
62
|
-
|
|
63
|
-
const serialized = command.toJSON();
|
|
64
|
-
expect(serialized).toBeDefined();
|
|
65
|
-
expect(serialized.aseid).toBe(command.aseid.toString());
|
|
66
|
-
expect(serialized.code).toBe(command.code);
|
|
67
|
-
expect(serialized.status).toBe(command.status);
|
|
68
|
-
expect(serialized.startedAt).toBe(command.startedAt?.toISOString());
|
|
69
|
-
expect(serialized.duration).toBe(command.duration);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const deserializedCommand = new A_Command(serialized);
|
|
73
|
-
expect(deserializedCommand).toBeInstanceOf(A_Command);
|
|
74
|
-
expect(deserializedCommand.aseid.toString()).toBe(command.aseid.toString());
|
|
75
|
-
expect(deserializedCommand.code).toBe(command.code);
|
|
76
|
-
expect(deserializedCommand.status).toBe(command.status);
|
|
77
|
-
expect(deserializedCommand.startedAt?.toISOString()).toBe(command.startedAt?.toISOString());
|
|
78
|
-
expect(deserializedCommand.duration).toBe(command.duration);
|
|
79
|
-
});
|
|
41
|
+
it('Should allow to create a command with parameters', () => {
|
|
42
|
+
const params = { userId: '123', action: 'create' };
|
|
43
|
+
const command = new TestCommand(params);
|
|
80
44
|
|
|
81
|
-
|
|
45
|
+
expect(command).toBeInstanceOf(A_Command);
|
|
46
|
+
expect(command.params).toEqual(params);
|
|
47
|
+
expect(command.status).toBe(A_Command_Status.CREATED);
|
|
48
|
+
expect(command.result).toBeUndefined();
|
|
49
|
+
expect(command.error).toBeUndefined();
|
|
50
|
+
});
|
|
82
51
|
|
|
83
|
-
|
|
84
|
-
|
|
52
|
+
it('Should have proper initial state and timestamps', () => {
|
|
53
|
+
const params = { userId: '123', action: 'create' };
|
|
54
|
+
const command = new TestCommand(params);
|
|
85
55
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
56
|
+
expect(command.status).toBe(A_Command_Status.CREATED);
|
|
57
|
+
expect(command.createdAt).toBeInstanceOf(Date);
|
|
58
|
+
expect(command.startedAt).toBeUndefined();
|
|
59
|
+
expect(command.endedAt).toBeUndefined();
|
|
60
|
+
expect(command.duration).toBeUndefined();
|
|
61
|
+
expect(command.idleTime).toBeUndefined();
|
|
62
|
+
expect(command.isProcessed).toBe(false);
|
|
63
|
+
});
|
|
90
64
|
|
|
91
|
-
|
|
65
|
+
it('Should have unique command code based on class name', () => {
|
|
66
|
+
const command = new TestCommand({ userId: '123', action: 'create' });
|
|
92
67
|
|
|
93
|
-
|
|
94
|
-
|
|
68
|
+
expect(command.code).toBe('test-command');
|
|
69
|
+
expect((TestCommand as any).code).toBe('test-command');
|
|
70
|
+
});
|
|
95
71
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
) {
|
|
100
|
-
context.set('bar', 'baz');
|
|
101
|
-
}
|
|
102
|
-
}
|
|
72
|
+
it('Should have event listener capabilities', () => {
|
|
73
|
+
const command = new TestCommand({ userId: '123', action: 'create' });
|
|
74
|
+
const listener = jest.fn();
|
|
103
75
|
|
|
104
|
-
|
|
105
|
-
|
|
76
|
+
command.on(A_CommandFeatures.onComplete, listener);
|
|
77
|
+
command.emit(A_CommandFeatures.onComplete);
|
|
106
78
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
A_Context.root.register(command);
|
|
79
|
+
expect(listener).toHaveBeenCalledWith(command);
|
|
80
|
+
});
|
|
110
81
|
|
|
111
|
-
|
|
112
|
-
|
|
82
|
+
it('Should support removing event listeners', () => {
|
|
83
|
+
const command = new TestCommand({ userId: '123', action: 'create' });
|
|
84
|
+
const listener = jest.fn();
|
|
113
85
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
expect(command.result).toEqual({ bar: 'baz' });
|
|
118
|
-
})
|
|
119
|
-
it('Should allow to fail a command with custom logic', async () => {
|
|
120
|
-
// 1) reset context to have a clean scope
|
|
121
|
-
A_Context.reset();
|
|
86
|
+
command.on(A_CommandFeatures.onComplete, listener);
|
|
87
|
+
command.off(A_CommandFeatures.onComplete, listener);
|
|
88
|
+
command.emit(A_CommandFeatures.onComplete);
|
|
122
89
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
class MyCommand extends A_Command<invokeType, resultType> { }
|
|
90
|
+
expect(listener).not.toHaveBeenCalled();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
127
93
|
|
|
128
|
-
|
|
94
|
+
// =============================================================================
|
|
95
|
+
// ======================== Command Lifecycle Tests ==========================
|
|
96
|
+
// =============================================================================
|
|
129
97
|
|
|
130
|
-
|
|
131
|
-
|
|
98
|
+
describe('Command Lifecycle', () => {
|
|
99
|
+
interface UserCommandParams {
|
|
100
|
+
userId: string;
|
|
101
|
+
}
|
|
132
102
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
) {
|
|
137
|
-
context.error(new A_Error({ title: 'Test error' }));
|
|
138
|
-
// it's optional to throw an error here, as the command may contain multiple errors that also can be a result of async operations
|
|
139
|
-
throw new A_Error({ title: 'Test error thrown' });
|
|
140
|
-
}
|
|
103
|
+
interface UserCommandResult {
|
|
104
|
+
success: boolean;
|
|
105
|
+
data: any;
|
|
141
106
|
}
|
|
142
107
|
|
|
143
|
-
|
|
144
|
-
A_Context.root.register(MyComponent);
|
|
145
|
-
// 5) create a new command instance within the scope
|
|
146
|
-
const command = new MyCommand({ foo: 'bar' });
|
|
147
|
-
A_Context.root.register(command);
|
|
108
|
+
class UserCommand extends A_Command<UserCommandParams, UserCommandResult> { }
|
|
148
109
|
|
|
149
|
-
|
|
150
|
-
|
|
110
|
+
it('Should properly initialize command with scope', async () => {
|
|
111
|
+
const command = new UserCommand({ userId: '123' });
|
|
112
|
+
A_Context.root.register(command);
|
|
151
113
|
|
|
152
|
-
|
|
153
|
-
expect(command.status).toBe(A_CONSTANTS__A_Command_Status.FAILED);
|
|
154
|
-
expect(command.errors).toBeDefined();
|
|
155
|
-
expect(command.errors?.size).toBe(1);
|
|
156
|
-
expect(Array.from(command.errors?.values() || [])[0].message).toBe('Test error');
|
|
157
|
-
});
|
|
114
|
+
await command.init();
|
|
158
115
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
A_Context.reset();
|
|
116
|
+
expect(command.status).toBe(A_Command_Status.INITIALIZED);
|
|
117
|
+
expect(command.scope).toBeInstanceOf(A_Scope);
|
|
162
118
|
});
|
|
163
119
|
|
|
164
|
-
it('Should
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
class TestCommand extends A_Command<{ input: string }, { output: string }> {}
|
|
168
|
-
|
|
169
|
-
const command = new TestCommand({ input: 'test' });
|
|
120
|
+
it('Should transition through proper lifecycle states during execution', async () => {
|
|
121
|
+
const command = new UserCommand({ userId: '123' });
|
|
170
122
|
A_Context.root.register(command);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
command.on(A_CommandFeatures.onInit, () => lifecycleOrder.push('init'));
|
|
174
|
-
command.on(A_CommandFeatures.onCompile, () => lifecycleOrder.push('compile'));
|
|
175
|
-
command.on(A_CommandFeatures.onExecute, () => lifecycleOrder.push('execute'));
|
|
176
|
-
command.on(A_CommandFeatures.onComplete, () => lifecycleOrder.push('complete'));
|
|
177
|
-
command.on(A_CommandFeatures.onFail, () => lifecycleOrder.push('fail'));
|
|
123
|
+
|
|
124
|
+
expect(command.status).toBe(A_Command_Status.CREATED);
|
|
178
125
|
|
|
179
126
|
await command.execute();
|
|
180
127
|
|
|
181
|
-
expect(
|
|
182
|
-
expect(command.
|
|
128
|
+
expect(command.status).toBe(A_Command_Status.COMPLETED);
|
|
129
|
+
expect(command.startedAt).toBeInstanceOf(Date);
|
|
130
|
+
expect(command.endedAt).toBeInstanceOf(Date);
|
|
131
|
+
expect(command.duration).toBeGreaterThanOrEqual(0);
|
|
132
|
+
expect(command.isProcessed).toBe(true);
|
|
183
133
|
});
|
|
184
134
|
|
|
185
|
-
it('Should
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
class TestCommand extends A_Command<{ input: string }, { output: string }> {}
|
|
189
|
-
|
|
190
|
-
const command = new TestCommand({ input: 'test' });
|
|
135
|
+
it('Should handle command completion with result', async () => {
|
|
136
|
+
const command = new UserCommand({ userId: '123' });
|
|
191
137
|
A_Context.root.register(command);
|
|
192
138
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
statusChanges.push(command.status);
|
|
139
|
+
const result = { success: true, data: { id: '123', name: 'Test User' } };
|
|
140
|
+
await command.complete(result);
|
|
196
141
|
|
|
197
|
-
|
|
198
|
-
expect(command.
|
|
199
|
-
|
|
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
|
-
]);
|
|
142
|
+
expect(command.status).toBe(A_Command_Status.COMPLETED);
|
|
143
|
+
expect(command.result).toEqual(result);
|
|
144
|
+
expect(command.isProcessed).toBe(true);
|
|
215
145
|
});
|
|
216
146
|
|
|
217
|
-
it('Should handle
|
|
218
|
-
|
|
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_CommandFeatures.onExecute]() {
|
|
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' });
|
|
147
|
+
it('Should handle command failure with error', async () => {
|
|
148
|
+
const command = new UserCommand({ userId: '123' });
|
|
232
149
|
A_Context.root.register(command);
|
|
233
150
|
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
command.on(A_CommandFeatures.onComplete, () => lifecycleOrder.push('complete'));
|
|
239
|
-
command.on(A_CommandFeatures.onFail, () => lifecycleOrder.push('fail'));
|
|
151
|
+
const error = new A_CommandError({
|
|
152
|
+
title: 'Test Error',
|
|
153
|
+
description: 'Test error description'
|
|
154
|
+
});
|
|
240
155
|
|
|
241
|
-
await command.
|
|
156
|
+
await command.fail(error);
|
|
242
157
|
|
|
243
|
-
expect(
|
|
244
|
-
expect(command.
|
|
245
|
-
expect(command.
|
|
246
|
-
expect(command.isCompleted).toBe(false);
|
|
158
|
+
expect(command.status).toBe(A_Command_Status.FAILED);
|
|
159
|
+
expect(command.error).toEqual(error);
|
|
160
|
+
expect(command.isProcessed).toBe(true);
|
|
247
161
|
});
|
|
248
162
|
|
|
249
|
-
it('Should
|
|
250
|
-
const command = new
|
|
163
|
+
it('Should not allow execution if already processed', async () => {
|
|
164
|
+
const command = new UserCommand({ userId: '123' });
|
|
251
165
|
A_Context.root.register(command);
|
|
252
166
|
|
|
253
|
-
|
|
254
|
-
expect(command.
|
|
255
|
-
expect(command.duration).toBeUndefined();
|
|
167
|
+
await command.complete({ success: true, data: null });
|
|
168
|
+
expect(command.isProcessed).toBe(true);
|
|
256
169
|
|
|
257
|
-
|
|
170
|
+
// Should not change state on second execution attempt
|
|
258
171
|
await command.execute();
|
|
259
|
-
|
|
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());
|
|
172
|
+
expect(command.status).toBe(A_Command_Status.COMPLETED);
|
|
267
173
|
});
|
|
268
174
|
});
|
|
269
175
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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);
|
|
176
|
+
// =============================================================================
|
|
177
|
+
// ======================== Command Serialization Tests ======================
|
|
178
|
+
// =============================================================================
|
|
278
179
|
|
|
279
|
-
|
|
280
|
-
|
|
180
|
+
describe('Command Serialization', () => {
|
|
181
|
+
interface SerializationTestParams {
|
|
182
|
+
itemId: string;
|
|
183
|
+
quantity: number;
|
|
184
|
+
}
|
|
281
185
|
|
|
282
|
-
|
|
283
|
-
|
|
186
|
+
interface SerializationTestResult {
|
|
187
|
+
processed: boolean;
|
|
188
|
+
total: number;
|
|
189
|
+
}
|
|
284
190
|
|
|
285
|
-
|
|
191
|
+
class SerializationTestCommand extends A_Command<SerializationTestParams, SerializationTestResult> {
|
|
192
|
+
customProperty?: string;
|
|
286
193
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
194
|
+
toJSON(): A_TYPES__Command_Serialized<SerializationTestParams, SerializationTestResult> & { customProperty?: string } {
|
|
195
|
+
return {
|
|
196
|
+
...super.toJSON(),
|
|
197
|
+
customProperty: this.customProperty
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
290
201
|
|
|
291
|
-
it('Should
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
class CustomCommand extends A_Command<{}, {}, CustomEvents> {}
|
|
295
|
-
|
|
296
|
-
const command = new CustomCommand({});
|
|
202
|
+
it('Should serialize command to JSON properly', async () => {
|
|
203
|
+
const params = { itemId: '999', quantity: 5 };
|
|
204
|
+
const command = new SerializationTestCommand(params);
|
|
297
205
|
A_Context.root.register(command);
|
|
298
206
|
|
|
299
|
-
|
|
300
|
-
const customEvent2Calls: number[] = [];
|
|
207
|
+
command.customProperty = 'test-value';
|
|
301
208
|
|
|
302
|
-
|
|
303
|
-
command.
|
|
209
|
+
const result = { processed: true, total: 100 };
|
|
210
|
+
await command.complete(result);
|
|
304
211
|
|
|
305
|
-
command.
|
|
306
|
-
command.emit('custom-event-2');
|
|
307
|
-
command.emit('custom-event-1');
|
|
212
|
+
const serialized = command.toJSON();
|
|
308
213
|
|
|
309
|
-
expect(
|
|
310
|
-
expect(
|
|
214
|
+
expect(serialized.code).toBe('serialization-test-command');
|
|
215
|
+
expect(serialized.status).toBe(A_Command_Status.COMPLETED);
|
|
216
|
+
expect(serialized.params).toEqual(params);
|
|
217
|
+
expect(serialized.result).toEqual(result);
|
|
218
|
+
expect(serialized.createdAt).toBeDefined();
|
|
219
|
+
expect((serialized as any).customProperty).toBe('test-value');
|
|
311
220
|
});
|
|
312
221
|
|
|
313
|
-
it('Should
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const
|
|
222
|
+
it('Should create command from serialized data', () => {
|
|
223
|
+
// Create a valid ASEID first
|
|
224
|
+
const existingCommand = new SerializationTestCommand({ itemId: '123', quantity: 1 });
|
|
225
|
+
const validAseid = existingCommand.aseid.toString();
|
|
226
|
+
|
|
227
|
+
const serializedData: A_TYPES__Command_Serialized<SerializationTestParams, SerializationTestResult> = {
|
|
228
|
+
aseid: validAseid,
|
|
229
|
+
code: 'serialization-test-command',
|
|
230
|
+
status: A_Command_Status.COMPLETED,
|
|
231
|
+
params: { itemId: '999', quantity: 5 },
|
|
232
|
+
result: { processed: true, total: 100 },
|
|
233
|
+
createdAt: new Date().toISOString(),
|
|
234
|
+
startedAt: new Date().toISOString(),
|
|
235
|
+
endedAt: new Date().toISOString(),
|
|
236
|
+
duration: 1000,
|
|
237
|
+
idleTime: 100
|
|
238
|
+
};
|
|
319
239
|
|
|
320
|
-
command
|
|
321
|
-
await command.init();
|
|
322
|
-
expect(listenerCalls).toEqual([1]);
|
|
240
|
+
const command = new SerializationTestCommand(serializedData);
|
|
323
241
|
|
|
324
|
-
|
|
325
|
-
command.
|
|
326
|
-
|
|
327
|
-
|
|
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
|
|
242
|
+
expect(command.params).toEqual(serializedData.params);
|
|
243
|
+
expect(command.status).toBe(A_Command_Status.COMPLETED);
|
|
244
|
+
expect(command.result).toEqual(serializedData.result);
|
|
245
|
+
expect(command.createdAt).toBeInstanceOf(Date);
|
|
331
246
|
});
|
|
247
|
+
});
|
|
332
248
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
249
|
+
// =============================================================================
|
|
250
|
+
// ======================== Component Integration Tests =======================
|
|
251
|
+
// =============================================================================
|
|
336
252
|
|
|
337
|
-
|
|
253
|
+
describe('Component Integration', () => {
|
|
254
|
+
interface ComponentTestParams {
|
|
255
|
+
userId: string;
|
|
256
|
+
}
|
|
338
257
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
258
|
+
interface ComponentTestResult {
|
|
259
|
+
userInfo: any;
|
|
260
|
+
processed: boolean;
|
|
261
|
+
}
|
|
342
262
|
|
|
343
|
-
|
|
263
|
+
class ComponentTestCommand extends A_Command<ComponentTestParams, ComponentTestResult> { }
|
|
344
264
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
265
|
+
class TestProcessor extends A_Component {
|
|
266
|
+
@A_Feature.Extend()
|
|
267
|
+
async [A_CommandFeatures.onBeforeExecute](
|
|
268
|
+
@A_Inject(A_Caller) command: ComponentTestCommand
|
|
269
|
+
) {
|
|
270
|
+
testExecutionLog.push('Pre-processing command');
|
|
271
|
+
}
|
|
348
272
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
273
|
+
@A_Feature.Extend()
|
|
274
|
+
async [A_CommandFeatures.onExecute](
|
|
275
|
+
@A_Inject(A_Caller) command: ComponentTestCommand
|
|
276
|
+
) {
|
|
277
|
+
testExecutionLog.push('Executing command');
|
|
278
|
+
const result = {
|
|
279
|
+
userInfo: { id: command.params.userId, name: 'Test User' },
|
|
280
|
+
processed: true
|
|
281
|
+
};
|
|
282
|
+
await command.complete(result);
|
|
283
|
+
}
|
|
352
284
|
|
|
353
|
-
|
|
285
|
+
@A_Feature.Extend()
|
|
286
|
+
async [A_CommandFeatures.onAfterExecute](
|
|
287
|
+
@A_Inject(A_Caller) command: ComponentTestCommand
|
|
288
|
+
) {
|
|
289
|
+
testExecutionLog.push('Post-processing command');
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
it('Should execute with component processors', async () => {
|
|
294
|
+
const container = new A_Container({
|
|
295
|
+
name: 'Test Container',
|
|
296
|
+
components: [
|
|
297
|
+
TestProcessor,
|
|
298
|
+
A_StateMachine
|
|
299
|
+
],
|
|
300
|
+
entities: [ComponentTestCommand]
|
|
301
|
+
});
|
|
354
302
|
|
|
355
|
-
|
|
356
|
-
|
|
303
|
+
const concept = new A_Concept({
|
|
304
|
+
containers: [container]
|
|
357
305
|
});
|
|
358
|
-
command.on(A_CommandFeatures.onInit, () => successfulCalls.push(1));
|
|
359
306
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
307
|
+
await concept.load();
|
|
308
|
+
|
|
309
|
+
const command = new ComponentTestCommand({ userId: '123' });
|
|
310
|
+
container.scope.register(command);
|
|
311
|
+
|
|
312
|
+
await command.execute();
|
|
313
|
+
|
|
314
|
+
expect(command.status).toBe(A_Command_Status.COMPLETED);
|
|
315
|
+
expect(command.result).toEqual({
|
|
316
|
+
userInfo: { id: '123', name: 'Test User' },
|
|
317
|
+
processed: true
|
|
318
|
+
});
|
|
363
319
|
});
|
|
364
320
|
});
|
|
365
321
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
322
|
+
// =============================================================================
|
|
323
|
+
// ======================== Multi-Service Communication Tests ================
|
|
324
|
+
// =============================================================================
|
|
325
|
+
|
|
326
|
+
describe('Multi-Service Communication', () => {
|
|
327
|
+
interface MultiServiceParams {
|
|
328
|
+
orderId: string;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
interface MultiServiceResult {
|
|
332
|
+
orderStatus: string;
|
|
333
|
+
processed: boolean;
|
|
334
|
+
}
|
|
370
335
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
objectParam: {
|
|
377
|
-
nested: string;
|
|
378
|
-
array: number[];
|
|
336
|
+
class MultiServiceCommand extends A_Command<MultiServiceParams, MultiServiceResult> {
|
|
337
|
+
toJSON(): A_TYPES__Command_Serialized<MultiServiceParams, MultiServiceResult> & { orderId: string } {
|
|
338
|
+
return {
|
|
339
|
+
...super.toJSON(),
|
|
340
|
+
orderId: this.params.orderId
|
|
379
341
|
};
|
|
380
|
-
arrayParam: string[];
|
|
381
|
-
dateParam: string; // ISO string representation
|
|
382
|
-
nullParam: null;
|
|
383
|
-
undefinedParam?: undefined;
|
|
384
342
|
}
|
|
343
|
+
}
|
|
385
344
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
dateParam: new Date('2023-01-01').toISOString(),
|
|
396
|
-
nullParam: null
|
|
397
|
-
};
|
|
345
|
+
class ServiceAProcessor extends A_Component {
|
|
346
|
+
@A_Feature.Extend()
|
|
347
|
+
async [A_CommandFeatures.onBeforeExecute](
|
|
348
|
+
@A_Inject(A_Caller) command: MultiServiceCommand,
|
|
349
|
+
@A_Inject(A_Memory) memory: A_Memory<{ orderData: any }>
|
|
350
|
+
) {
|
|
351
|
+
testExecutionLog.push('ServiceA: Pre-processing');
|
|
352
|
+
await memory.set('orderData', { id: command.params.orderId, status: 'processing' });
|
|
353
|
+
}
|
|
398
354
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
355
|
+
@A_Feature.Extend()
|
|
356
|
+
async [A_CommandFeatures.onExecute](
|
|
357
|
+
@A_Inject(A_Caller) command: MultiServiceCommand,
|
|
358
|
+
@A_Inject(A_Channel) channel: A_Channel
|
|
359
|
+
) {
|
|
360
|
+
testExecutionLog.push('ServiceA: Routing to ServiceB');
|
|
405
361
|
|
|
406
|
-
|
|
407
|
-
|
|
362
|
+
const response = await channel.request<any, A_TYPES__Command_Serialized<MultiServiceParams, MultiServiceResult>>({
|
|
363
|
+
container: 'ServiceB',
|
|
364
|
+
command: command.toJSON()
|
|
365
|
+
});
|
|
408
366
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
expect(deserializedCommand.params.objectParam.nested).toBe('nested value');
|
|
413
|
-
expect(deserializedCommand.params.arrayParam).toEqual(['a', 'b', 'c']);
|
|
414
|
-
});
|
|
367
|
+
command.fromJSON(response.data!);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
415
370
|
|
|
416
|
-
|
|
417
|
-
|
|
371
|
+
class ServiceBProcessor extends A_Component {
|
|
372
|
+
@A_Feature.Extend()
|
|
373
|
+
async [A_CommandFeatures.onBeforeExecute](
|
|
374
|
+
@A_Inject(A_Caller) command: MultiServiceCommand,
|
|
375
|
+
@A_Inject(A_Memory) memory: A_Memory<{ orderData: any }>
|
|
376
|
+
) {
|
|
377
|
+
testExecutionLog.push('ServiceB: Pre-processing');
|
|
378
|
+
const orderData = await memory.get('orderData');
|
|
379
|
+
expect(orderData).toBeDefined();
|
|
380
|
+
}
|
|
418
381
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
382
|
+
@A_Feature.Extend()
|
|
383
|
+
async [A_CommandFeatures.onExecute](
|
|
384
|
+
@A_Inject(A_Caller) command: MultiServiceCommand
|
|
385
|
+
) {
|
|
386
|
+
testExecutionLog.push('ServiceB: Processing command');
|
|
387
|
+
await command.complete({
|
|
388
|
+
orderStatus: 'completed',
|
|
389
|
+
processed: true
|
|
390
|
+
});
|
|
426
391
|
}
|
|
392
|
+
}
|
|
427
393
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
@
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
)
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
394
|
+
class TestChannel extends A_Channel {
|
|
395
|
+
async onRequest(
|
|
396
|
+
@A_Inject(A_Memory) memory: A_Memory<{ containers: Array<A_Container> }>,
|
|
397
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest<{ container: string, command: A_TYPES__Command_Serialized }>
|
|
398
|
+
): Promise<void> {
|
|
399
|
+
const containers = await memory.get('containers') || [];
|
|
400
|
+
const target = containers.find(c => c.name === context.params.container);
|
|
401
|
+
|
|
402
|
+
if (!target) {
|
|
403
|
+
throw new A_Error(`Container ${context.params.container} not found`);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const commandConstructor = target.scope.resolveConstructor<A_Command>(context.params.command.code);
|
|
407
|
+
if (!commandConstructor) {
|
|
408
|
+
throw new A_Error(`Command constructor not found: ${context.params.command.code}`);
|
|
441
409
|
}
|
|
410
|
+
|
|
411
|
+
const command = new commandConstructor(context.params.command);
|
|
412
|
+
target.scope.register(command);
|
|
413
|
+
|
|
414
|
+
await command.execute();
|
|
415
|
+
context.succeed(command.toJSON());
|
|
442
416
|
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
class TestService extends A_Container {
|
|
420
|
+
@A_Concept.Load()
|
|
421
|
+
async init(
|
|
422
|
+
@A_Inject(A_Memory) memory: A_Memory<{ containers: Array<A_Container> }>
|
|
423
|
+
) {
|
|
424
|
+
const containers = await memory.get('containers') || [];
|
|
425
|
+
containers.push(this);
|
|
426
|
+
await memory.set('containers', containers);
|
|
427
|
+
testExecutionLog.push(`Registered container: ${this.name}`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
it('Should handle multi-service command routing', async () => {
|
|
432
|
+
const sharedMemory = new A_MemoryContext();
|
|
433
|
+
|
|
434
|
+
const serviceA = new TestService({
|
|
435
|
+
name: 'ServiceA',
|
|
436
|
+
components: [
|
|
437
|
+
ServiceAProcessor,
|
|
438
|
+
TestChannel,
|
|
439
|
+
A_Memory,
|
|
440
|
+
A_StateMachine
|
|
441
|
+
],
|
|
442
|
+
entities: [MultiServiceCommand],
|
|
443
|
+
fragments: [sharedMemory]
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const serviceB = new TestService({
|
|
447
|
+
name: 'ServiceB',
|
|
448
|
+
components: [
|
|
449
|
+
ServiceBProcessor,
|
|
450
|
+
A_Memory,
|
|
451
|
+
A_StateMachine
|
|
452
|
+
],
|
|
453
|
+
entities: [MultiServiceCommand],
|
|
454
|
+
fragments: [sharedMemory]
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
const concept = new A_Concept({
|
|
458
|
+
containers: [serviceA, serviceB],
|
|
459
|
+
components: [A_Memory, TestChannel]
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
await concept.load();
|
|
463
|
+
|
|
464
|
+
const command = new MultiServiceCommand({ orderId: '999' });
|
|
465
|
+
serviceA.scope.register(command);
|
|
443
466
|
|
|
444
|
-
A_Context.root.register(ResultProcessor);
|
|
445
|
-
|
|
446
|
-
const command = new ResultCommand({ input: 'test-input' });
|
|
447
|
-
A_Context.root.register(command);
|
|
448
|
-
|
|
449
467
|
await command.execute();
|
|
450
468
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
expect(
|
|
454
|
-
expect(
|
|
455
|
-
expect(
|
|
456
|
-
expect(
|
|
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);
|
|
469
|
+
expect(command.status).toBe(A_Command_Status.COMPLETED);
|
|
470
|
+
// The result might be set by ServiceB processor but we need to check the execution log
|
|
471
|
+
expect(testExecutionLog).toContain('ServiceA: Pre-processing');
|
|
472
|
+
expect(testExecutionLog).toContain('ServiceA: Routing to ServiceB');
|
|
473
|
+
expect(testExecutionLog).toContain('ServiceB: Pre-processing');
|
|
474
|
+
expect(testExecutionLog).toContain('ServiceB: Processing command');
|
|
464
475
|
});
|
|
476
|
+
});
|
|
465
477
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
478
|
+
// =============================================================================
|
|
479
|
+
// ======================== Feature Template Tests ===========================
|
|
480
|
+
// =============================================================================
|
|
481
|
+
|
|
482
|
+
describe('Feature Template Processing', () => {
|
|
483
|
+
interface TemplateTestParams {
|
|
484
|
+
itemId: string;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
interface TemplateTestResult {
|
|
488
|
+
itemName: string;
|
|
489
|
+
itemPrice: number;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
class TemplateTestCommand extends A_Command<TemplateTestParams, TemplateTestResult> {
|
|
493
|
+
@A_Feature.Define({
|
|
494
|
+
template: [
|
|
495
|
+
{
|
|
496
|
+
name: 'itemName',
|
|
497
|
+
component: 'ItemNameHandler',
|
|
498
|
+
handler: 'getName'
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
name: 'itemPrice',
|
|
502
|
+
component: 'ItemPriceHandler',
|
|
503
|
+
handler: 'getPrice'
|
|
504
|
+
}
|
|
505
|
+
]
|
|
506
|
+
})
|
|
507
|
+
protected async [A_CommandFeatures.onExecute](): Promise<void> {
|
|
508
|
+
testExecutionLog.push('Executing template-based command');
|
|
486
509
|
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
class ItemNameHandler extends A_Component {
|
|
513
|
+
getName() {
|
|
514
|
+
testExecutionLog.push('Getting item name');
|
|
515
|
+
return 'Test Item';
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
class ItemPriceHandler extends A_Component {
|
|
520
|
+
getPrice() {
|
|
521
|
+
testExecutionLog.push('Getting item price');
|
|
522
|
+
return 99.99;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
it('Should process feature templates correctly', async () => {
|
|
527
|
+
const container = new A_Container({
|
|
528
|
+
name: 'Template Test Container',
|
|
529
|
+
components: [
|
|
530
|
+
ItemNameHandler,
|
|
531
|
+
ItemPriceHandler,
|
|
532
|
+
A_StateMachine
|
|
533
|
+
],
|
|
534
|
+
entities: [TemplateTestCommand]
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
const concept = new A_Concept({
|
|
538
|
+
containers: [container]
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
await concept.load();
|
|
542
|
+
|
|
543
|
+
const command = new TemplateTestCommand({ itemId: '123' });
|
|
544
|
+
container.scope.register(command);
|
|
487
545
|
|
|
488
|
-
A_Context.root.register(ErrorComponent);
|
|
489
|
-
|
|
490
|
-
const command = new ErrorCommand({ input: 'test' });
|
|
491
|
-
A_Context.root.register(command);
|
|
492
|
-
|
|
493
546
|
await command.execute();
|
|
494
547
|
|
|
495
|
-
expect(command.
|
|
496
|
-
|
|
548
|
+
expect(command.status).toBe(A_Command_Status.COMPLETED);
|
|
549
|
+
// Note: The actual template processing depends on the A_Feature implementation
|
|
550
|
+
// This test validates the structure and execution flow
|
|
551
|
+
});
|
|
552
|
+
});
|
|
497
553
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
554
|
+
// =============================================================================
|
|
555
|
+
// ======================== Error Handling Tests =============================
|
|
556
|
+
// =============================================================================
|
|
557
|
+
|
|
558
|
+
describe('Error Handling', () => {
|
|
559
|
+
interface ErrorTestParams {
|
|
560
|
+
shouldFail: boolean;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
class ErrorTestCommand extends A_Command<ErrorTestParams, any> { }
|
|
564
|
+
|
|
565
|
+
class FailingProcessor extends A_Component {
|
|
566
|
+
@A_Feature.Extend()
|
|
567
|
+
async [A_CommandFeatures.onExecute](
|
|
568
|
+
@A_Inject(A_Caller) command: ErrorTestCommand
|
|
569
|
+
) {
|
|
570
|
+
if (command.params.shouldFail) {
|
|
571
|
+
throw new Error('Simulated execution error');
|
|
572
|
+
}
|
|
573
|
+
await command.complete({ success: true });
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
it('Should handle execution errors properly', async () => {
|
|
578
|
+
const container = new A_Container({
|
|
579
|
+
name: 'Error Test Container',
|
|
580
|
+
components: [
|
|
581
|
+
FailingProcessor,
|
|
582
|
+
A_StateMachine
|
|
583
|
+
],
|
|
584
|
+
entities: [ErrorTestCommand]
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
const concept = new A_Concept({
|
|
588
|
+
containers: [container]
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
await concept.load();
|
|
592
|
+
|
|
593
|
+
const command = new ErrorTestCommand({ shouldFail: true });
|
|
594
|
+
container.scope.register(command);
|
|
595
|
+
|
|
596
|
+
await command.execute();
|
|
597
|
+
|
|
598
|
+
expect(command.status).toBe(A_Command_Status.FAILED);
|
|
599
|
+
expect(command.error).toBeDefined();
|
|
600
|
+
expect(command.isProcessed).toBe(true);
|
|
511
601
|
});
|
|
512
602
|
|
|
513
|
-
it('Should
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
email: 'test@example.com',
|
|
520
|
-
preferences: {
|
|
521
|
-
theme: 'dark',
|
|
522
|
-
notifications: true
|
|
523
|
-
}
|
|
524
|
-
},
|
|
525
|
-
timestamp: new Date().toISOString()
|
|
526
|
-
};
|
|
603
|
+
it('Should handle execution without explicit scope binding', async () => {
|
|
604
|
+
const command = new ErrorTestCommand({ shouldFail: false });
|
|
605
|
+
|
|
606
|
+
// Don't register in any scope - the command will execute but
|
|
607
|
+
// without component processors it will complete with default behavior
|
|
608
|
+
await command.execute();
|
|
527
609
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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);
|
|
610
|
+
// Since there's no processor to call complete(), the command might
|
|
611
|
+
// be in COMPLETED or FAILED state depending on internal logic
|
|
612
|
+
expect([A_Command_Status.COMPLETED, A_Command_Status.FAILED])
|
|
613
|
+
.toContain(command.status);
|
|
548
614
|
});
|
|
615
|
+
});
|
|
549
616
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
emptyArray: [],
|
|
554
|
-
emptyObject: {},
|
|
555
|
-
zeroNumber: 0,
|
|
556
|
-
falseBoolean: false,
|
|
557
|
-
nullValue: null
|
|
558
|
-
};
|
|
617
|
+
// =============================================================================
|
|
618
|
+
// ======================== Performance and Timing Tests ====================
|
|
619
|
+
// =============================================================================
|
|
559
620
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
621
|
+
describe('Performance and Timing', () => {
|
|
622
|
+
interface TimingTestParams {
|
|
623
|
+
delay: number;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
class TimingTestCommand extends A_Command<TimingTestParams, any> { }
|
|
627
|
+
|
|
628
|
+
class DelayProcessor extends A_Component {
|
|
629
|
+
@A_Feature.Extend()
|
|
630
|
+
async [A_CommandFeatures.onExecute](
|
|
631
|
+
@A_Inject(A_Caller) command: TimingTestCommand
|
|
632
|
+
) {
|
|
633
|
+
await new Promise(resolve => setTimeout(resolve, command.params.delay));
|
|
634
|
+
await command.complete({ completed: true });
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
it('Should track execution timing correctly', async () => {
|
|
639
|
+
const container = new A_Container({
|
|
640
|
+
name: 'Timing Test Container',
|
|
641
|
+
components: [
|
|
642
|
+
DelayProcessor,
|
|
643
|
+
A_StateMachine
|
|
644
|
+
],
|
|
645
|
+
entities: [TimingTestCommand]
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
const concept = new A_Concept({
|
|
649
|
+
containers: [container]
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
await concept.load();
|
|
653
|
+
|
|
654
|
+
const command = new TimingTestCommand({ delay: 100 });
|
|
655
|
+
container.scope.register(command);
|
|
656
|
+
const startTime = Date.now();
|
|
564
657
|
await command.execute();
|
|
658
|
+
const endTime = Date.now();
|
|
565
659
|
|
|
566
|
-
|
|
567
|
-
expect(
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
expect(
|
|
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);
|
|
660
|
+
expect(command.duration).toBeGreaterThanOrEqual(100);
|
|
661
|
+
expect(command.duration).toBeLessThan(endTime - startTime + 50); // Allow some tolerance
|
|
662
|
+
expect(command.idleTime).toBeGreaterThanOrEqual(0);
|
|
663
|
+
expect(command.startedAt).toBeInstanceOf(Date);
|
|
664
|
+
expect(command.endedAt).toBeInstanceOf(Date);
|
|
577
665
|
});
|
|
578
666
|
});
|
|
579
667
|
});
|