@bluelibs/runner 2.2.3 → 3.0.0
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 +1315 -942
- package/dist/common.types.d.ts +20 -0
- package/dist/common.types.js +4 -0
- package/dist/common.types.js.map +1 -0
- package/dist/context.d.ts +34 -0
- package/dist/context.js +58 -0
- package/dist/context.js.map +1 -0
- package/dist/define.d.ts +22 -3
- package/dist/define.js +52 -8
- package/dist/define.js.map +1 -1
- package/dist/defs.d.ts +52 -31
- package/dist/defs.js +10 -2
- package/dist/defs.js.map +1 -1
- package/dist/errors.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/event.types.d.ts +18 -0
- package/dist/event.types.js +4 -0
- package/dist/event.types.js.map +1 -0
- package/dist/examples/registrator-example.d.ts +122 -0
- package/dist/examples/registrator-example.js +147 -0
- package/dist/examples/registrator-example.js.map +1 -0
- package/dist/globals/globalEvents.d.ts +41 -0
- package/dist/globals/globalEvents.js +94 -0
- package/dist/globals/globalEvents.js.map +1 -0
- package/dist/globals/globalMiddleware.d.ts +23 -0
- package/dist/globals/globalMiddleware.js +15 -0
- package/dist/globals/globalMiddleware.js.map +1 -0
- package/dist/globals/globalResources.d.ts +27 -0
- package/dist/globals/globalResources.js +47 -0
- package/dist/globals/globalResources.js.map +1 -0
- package/dist/globals/middleware/cache.middleware.d.ts +34 -0
- package/dist/globals/middleware/cache.middleware.js +85 -0
- package/dist/globals/middleware/cache.middleware.js.map +1 -0
- package/dist/globals/middleware/requireContext.middleware.d.ts +6 -0
- package/dist/globals/middleware/requireContext.middleware.js +25 -0
- package/dist/globals/middleware/requireContext.middleware.js.map +1 -0
- package/dist/globals/middleware/retry.middleware.d.ts +20 -0
- package/dist/globals/middleware/retry.middleware.js +34 -0
- package/dist/globals/middleware/retry.middleware.js.map +1 -0
- package/dist/globals/resources/queue.resource.d.ts +7 -0
- package/dist/globals/resources/queue.resource.js +31 -0
- package/dist/globals/resources/queue.resource.js.map +1 -0
- package/dist/index.d.ts +45 -9
- package/dist/index.js +14 -9
- package/dist/index.js.map +1 -1
- package/dist/middleware.types.d.ts +40 -0
- package/dist/middleware.types.js +4 -0
- package/dist/middleware.types.js.map +1 -0
- package/dist/models/DependencyProcessor.d.ts +2 -1
- package/dist/models/DependencyProcessor.js +11 -13
- package/dist/models/DependencyProcessor.js.map +1 -1
- package/dist/models/EventManager.d.ts +5 -0
- package/dist/models/EventManager.js +44 -2
- package/dist/models/EventManager.js.map +1 -1
- package/dist/models/Logger.d.ts +30 -12
- package/dist/models/Logger.js +130 -42
- package/dist/models/Logger.js.map +1 -1
- package/dist/models/OverrideManager.d.ts +13 -0
- package/dist/models/OverrideManager.js +70 -0
- package/dist/models/OverrideManager.js.map +1 -0
- package/dist/models/Queue.d.ts +25 -0
- package/dist/models/Queue.js +54 -0
- package/dist/models/Queue.js.map +1 -0
- package/dist/models/ResourceInitializer.d.ts +5 -2
- package/dist/models/ResourceInitializer.js +20 -14
- package/dist/models/ResourceInitializer.js.map +1 -1
- package/dist/models/Semaphore.d.ts +61 -0
- package/dist/models/Semaphore.js +166 -0
- package/dist/models/Semaphore.js.map +1 -0
- package/dist/models/Store.d.ts +17 -72
- package/dist/models/Store.js +71 -269
- package/dist/models/Store.js.map +1 -1
- package/dist/models/StoreConstants.d.ts +11 -0
- package/dist/models/StoreConstants.js +18 -0
- package/dist/models/StoreConstants.js.map +1 -0
- package/dist/models/StoreRegistry.d.ts +25 -0
- package/dist/models/StoreRegistry.js +171 -0
- package/dist/models/StoreRegistry.js.map +1 -0
- package/dist/models/StoreTypes.d.ts +21 -0
- package/dist/models/StoreTypes.js +3 -0
- package/dist/models/StoreTypes.js.map +1 -0
- package/dist/models/StoreValidator.d.ts +10 -0
- package/dist/models/StoreValidator.js +41 -0
- package/dist/models/StoreValidator.js.map +1 -0
- package/dist/models/TaskRunner.js +39 -24
- package/dist/models/TaskRunner.js.map +1 -1
- package/dist/models/VarStore.d.ts +17 -0
- package/dist/models/VarStore.js +60 -0
- package/dist/models/VarStore.js.map +1 -0
- package/dist/models/index.d.ts +3 -0
- package/dist/models/index.js +3 -0
- package/dist/models/index.js.map +1 -1
- package/dist/resource.types.d.ts +31 -0
- package/dist/resource.types.js +3 -0
- package/dist/resource.types.js.map +1 -0
- package/dist/run.d.ts +4 -1
- package/dist/run.js +6 -3
- package/dist/run.js.map +1 -1
- package/dist/symbols.d.ts +24 -0
- package/dist/symbols.js +29 -0
- package/dist/symbols.js.map +1 -0
- package/dist/task.types.d.ts +55 -0
- package/dist/task.types.js +23 -0
- package/dist/task.types.js.map +1 -0
- package/dist/tools/registratorId.d.ts +4 -0
- package/dist/tools/registratorId.js +40 -0
- package/dist/tools/registratorId.js.map +1 -0
- package/dist/tools/simpleHash.d.ts +9 -0
- package/dist/tools/simpleHash.js +34 -0
- package/dist/tools/simpleHash.js.map +1 -0
- package/dist/types/base-interfaces.d.ts +18 -0
- package/dist/types/base-interfaces.js +6 -0
- package/dist/types/base-interfaces.js.map +1 -0
- package/dist/types/base.d.ts +13 -0
- package/dist/types/base.js +3 -0
- package/dist/types/base.js.map +1 -0
- package/dist/types/dependencies.d.ts +22 -0
- package/dist/types/dependencies.js +3 -0
- package/dist/types/dependencies.js.map +1 -0
- package/dist/types/dependency-core.d.ts +14 -0
- package/dist/types/dependency-core.js +5 -0
- package/dist/types/dependency-core.js.map +1 -0
- package/dist/types/events.d.ts +52 -0
- package/dist/types/events.js +6 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/hooks.d.ts +16 -0
- package/dist/types/hooks.js +5 -0
- package/dist/types/hooks.js.map +1 -0
- package/dist/types/index.d.ts +14 -0
- package/dist/types/index.js +27 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/meta.d.ts +13 -0
- package/dist/types/meta.js +5 -0
- package/dist/types/meta.js.map +1 -0
- package/dist/types/middleware.d.ts +38 -0
- package/dist/types/middleware.js +6 -0
- package/dist/types/middleware.js.map +1 -0
- package/dist/types/registerable.d.ts +10 -0
- package/dist/types/registerable.js +5 -0
- package/dist/types/registerable.js.map +1 -0
- package/dist/types/resources.d.ts +44 -0
- package/dist/types/resources.js +5 -0
- package/dist/types/resources.js.map +1 -0
- package/dist/types/symbols.d.ts +24 -0
- package/dist/types/symbols.js +30 -0
- package/dist/types/symbols.js.map +1 -0
- package/dist/types/tasks.d.ts +41 -0
- package/dist/types/tasks.js +5 -0
- package/dist/types/tasks.js.map +1 -0
- package/dist/types/utilities.d.ts +7 -0
- package/dist/types/utilities.js +5 -0
- package/dist/types/utilities.js.map +1 -0
- package/package.json +10 -6
- package/src/__tests__/benchmark/benchmark.test.ts +1 -1
- package/src/__tests__/context.test.ts +91 -0
- package/src/__tests__/errors.test.ts +8 -5
- package/src/__tests__/globalEvents.test.ts +1 -1
- package/src/__tests__/globals/cache.middleware.test.ts +772 -0
- package/src/__tests__/globals/queue.resource.test.ts +141 -0
- package/src/__tests__/globals/requireContext.middleware.test.ts +98 -0
- package/src/__tests__/globals/retry.middleware.test.ts +157 -0
- package/src/__tests__/index.helper.test.ts +55 -0
- package/src/__tests__/models/EventManager.test.ts +144 -0
- package/src/__tests__/models/Logger.test.ts +291 -34
- package/src/__tests__/models/Queue.test.ts +189 -0
- package/src/__tests__/models/ResourceInitializer.test.ts +8 -6
- package/src/__tests__/models/Semaphore.test.ts +713 -0
- package/src/__tests__/models/Store.test.ts +40 -0
- package/src/__tests__/models/TaskRunner.test.ts +86 -5
- package/src/__tests__/run.middleware.test.ts +166 -12
- package/src/__tests__/run.overrides.test.ts +13 -10
- package/src/__tests__/run.test.ts +363 -12
- package/src/__tests__/setOutput.test.ts +244 -0
- package/src/__tests__/tools/getCallerFile.test.ts +9 -9
- package/src/__tests__/typesafety.test.ts +54 -39
- package/src/context.ts +86 -0
- package/src/define.ts +84 -14
- package/src/defs.ts +91 -41
- package/src/errors.ts +3 -1
- package/src/{globalEvents.ts → globals/globalEvents.ts} +13 -12
- package/src/globals/globalMiddleware.ts +14 -0
- package/src/{globalResources.ts → globals/globalResources.ts} +14 -10
- package/src/globals/middleware/cache.middleware.ts +115 -0
- package/src/globals/middleware/requireContext.middleware.ts +36 -0
- package/src/globals/middleware/retry.middleware.ts +56 -0
- package/src/globals/resources/queue.resource.ts +34 -0
- package/src/index.ts +9 -5
- package/src/models/DependencyProcessor.ts +36 -40
- package/src/models/EventManager.ts +45 -5
- package/src/models/Logger.ts +170 -48
- package/src/models/OverrideManager.ts +84 -0
- package/src/models/Queue.ts +66 -0
- package/src/models/ResourceInitializer.ts +38 -20
- package/src/models/Semaphore.ts +208 -0
- package/src/models/Store.ts +94 -342
- package/src/models/StoreConstants.ts +17 -0
- package/src/models/StoreRegistry.ts +217 -0
- package/src/models/StoreTypes.ts +46 -0
- package/src/models/StoreValidator.ts +38 -0
- package/src/models/TaskRunner.ts +53 -40
- package/src/models/index.ts +3 -0
- package/src/run.ts +7 -4
- package/src/__tests__/index.ts +0 -15
- package/src/examples/express-mongo/index.ts +0 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Logger, ILog, LogLevels } from "../../models/Logger";
|
|
2
2
|
import { EventManager } from "../../models/EventManager";
|
|
3
|
-
import { globalEvents } from "../../globalEvents";
|
|
4
|
-
import { mock } from "node:test";
|
|
3
|
+
import { globalEvents } from "../../globals/globalEvents";
|
|
5
4
|
|
|
6
5
|
describe("Logger", () => {
|
|
7
6
|
let logger: Logger;
|
|
@@ -16,15 +15,19 @@ describe("Logger", () => {
|
|
|
16
15
|
it("should emit a log event with correct data", async () => {
|
|
17
16
|
const testData = "Test log message";
|
|
18
17
|
const testLevel = "info";
|
|
19
|
-
mockEventManager.emit = jest.fn();
|
|
18
|
+
mockEventManager.emit = jest.fn().mockResolvedValue(undefined);
|
|
19
|
+
mockEventManager.hasListeners = jest.fn().mockReturnValue(true);
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
logger.log(testLevel, testData);
|
|
22
|
+
|
|
23
|
+
// Wait for setImmediate to execute
|
|
24
|
+
await new Promise(setImmediate);
|
|
22
25
|
|
|
23
26
|
expect(mockEventManager.emit).toHaveBeenCalledWith(
|
|
24
27
|
globalEvents.log,
|
|
25
28
|
expect.objectContaining({
|
|
26
29
|
level: testLevel,
|
|
27
|
-
|
|
30
|
+
message: testData,
|
|
28
31
|
timestamp: expect.any(Date),
|
|
29
32
|
}),
|
|
30
33
|
"unknown"
|
|
@@ -41,21 +44,28 @@ describe("Logger", () => {
|
|
|
41
44
|
"critical",
|
|
42
45
|
];
|
|
43
46
|
|
|
44
|
-
mockEventManager.emit = jest.fn();
|
|
47
|
+
mockEventManager.emit = jest.fn().mockResolvedValue(undefined);
|
|
48
|
+
mockEventManager.hasListeners = jest.fn().mockReturnValue(true);
|
|
45
49
|
|
|
46
50
|
for (const level of levels) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
expect(mockEventManager.emit).toHaveBeenCalledWith(
|
|
50
|
-
globalEvents.log,
|
|
51
|
-
expect.objectContaining({
|
|
52
|
-
level,
|
|
53
|
-
data: `Test ${level} message`,
|
|
54
|
-
timestamp: expect.any(Date),
|
|
55
|
-
}),
|
|
56
|
-
"testSource"
|
|
57
|
-
);
|
|
51
|
+
logger.log(level, `Test ${level} message`, { source: "testSource" });
|
|
58
52
|
}
|
|
53
|
+
|
|
54
|
+
// Wait for setImmediate to execute
|
|
55
|
+
await new Promise(setImmediate);
|
|
56
|
+
|
|
57
|
+
// Check all calls were made
|
|
58
|
+
expect(mockEventManager.emit).toHaveBeenCalledTimes(levels.length);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should not emit events when there are no listeners", () => {
|
|
62
|
+
mockEventManager.emit = jest.fn().mockResolvedValue(undefined);
|
|
63
|
+
mockEventManager.hasListeners = jest.fn().mockReturnValue(false);
|
|
64
|
+
|
|
65
|
+
logger.log("info", "Test message");
|
|
66
|
+
|
|
67
|
+
// Should not emit events when no listeners
|
|
68
|
+
expect(mockEventManager.emit).not.toHaveBeenCalled();
|
|
59
69
|
});
|
|
60
70
|
});
|
|
61
71
|
|
|
@@ -74,47 +84,179 @@ describe("Logger", () => {
|
|
|
74
84
|
const testLog: ILog = {
|
|
75
85
|
level: "info",
|
|
76
86
|
source: "test",
|
|
77
|
-
|
|
87
|
+
message: "Test log message",
|
|
78
88
|
timestamp: new Date("2023-01-01T00:00:00Z"),
|
|
79
89
|
};
|
|
80
90
|
|
|
81
91
|
await logger.print(testLog);
|
|
82
92
|
|
|
83
93
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
84
|
-
expect.stringContaining("
|
|
94
|
+
expect.stringContaining("INFO")
|
|
85
95
|
);
|
|
86
96
|
});
|
|
87
97
|
|
|
88
|
-
it("should handle Error objects in log data",
|
|
98
|
+
it("should handle Error objects in log data", () => {
|
|
89
99
|
const testError = new Error("Test error");
|
|
90
100
|
const testLog: ILog = {
|
|
91
101
|
level: "error",
|
|
92
|
-
|
|
102
|
+
message: "Operation failed",
|
|
103
|
+
error: {
|
|
104
|
+
name: testError.name,
|
|
105
|
+
message: testError.message,
|
|
106
|
+
stack: testError.stack,
|
|
107
|
+
},
|
|
93
108
|
timestamp: new Date("2023-01-01T00:00:00Z"),
|
|
94
109
|
};
|
|
95
110
|
|
|
96
|
-
|
|
111
|
+
logger.print(testLog);
|
|
112
|
+
|
|
113
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
114
|
+
expect.stringContaining("ERROR")
|
|
115
|
+
);
|
|
116
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
117
|
+
expect.stringContaining("Error: Error")
|
|
118
|
+
);
|
|
119
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
120
|
+
expect.stringContaining("Test error")
|
|
121
|
+
);
|
|
122
|
+
});
|
|
97
123
|
|
|
124
|
+
it("should handle error objects without stack trace", () => {
|
|
125
|
+
const testLog: ILog = {
|
|
126
|
+
level: "error",
|
|
127
|
+
message: "Operation failed",
|
|
128
|
+
error: {
|
|
129
|
+
name: "CustomError",
|
|
130
|
+
message: "Something went wrong",
|
|
131
|
+
},
|
|
132
|
+
timestamp: new Date("2023-01-01T00:00:00Z"),
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
logger.print(testLog);
|
|
136
|
+
|
|
137
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
138
|
+
expect.stringContaining("ERROR")
|
|
139
|
+
);
|
|
98
140
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
99
|
-
expect.stringContaining("Error:
|
|
141
|
+
expect.stringContaining("Error: CustomError")
|
|
100
142
|
);
|
|
101
143
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
102
|
-
expect.stringContaining("
|
|
144
|
+
expect.stringContaining("Something went wrong")
|
|
103
145
|
);
|
|
104
146
|
});
|
|
105
147
|
|
|
106
|
-
it("should pretty-print
|
|
148
|
+
it("should pretty-print structured data", () => {
|
|
107
149
|
const testObject = { key: "value", nested: { foo: "bar" } };
|
|
108
150
|
const testLog: ILog = {
|
|
109
151
|
level: "debug",
|
|
152
|
+
message: "Debug info",
|
|
110
153
|
data: testObject,
|
|
111
154
|
timestamp: new Date("2023-01-01T00:00:00Z"),
|
|
112
155
|
};
|
|
113
156
|
|
|
114
|
-
|
|
157
|
+
logger.print(testLog);
|
|
158
|
+
|
|
159
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
160
|
+
expect.stringContaining("DEBUG")
|
|
161
|
+
);
|
|
162
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
163
|
+
expect.stringContaining('"key": "value"')
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should handle object message by stringifying it", () => {
|
|
168
|
+
const objectMessage = { type: "user", action: "login" };
|
|
169
|
+
const testLog: ILog = {
|
|
170
|
+
level: "info",
|
|
171
|
+
message: objectMessage as any,
|
|
172
|
+
timestamp: new Date("2023-01-01T00:00:00Z"),
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
logger.print(testLog);
|
|
176
|
+
|
|
177
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
178
|
+
expect.stringContaining("INFO")
|
|
179
|
+
);
|
|
180
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
181
|
+
expect.stringContaining(JSON.stringify(objectMessage, null, 2))
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should print context information when present", () => {
|
|
186
|
+
const context = { userId: "123", requestId: "abc-def" };
|
|
187
|
+
const testLog: ILog = {
|
|
188
|
+
level: "warn",
|
|
189
|
+
message: "Warning message",
|
|
190
|
+
data: context,
|
|
191
|
+
timestamp: new Date("2023-01-01T00:00:00Z"),
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
logger.print(testLog);
|
|
195
|
+
|
|
196
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
197
|
+
expect.stringContaining("WARN")
|
|
198
|
+
);
|
|
199
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
200
|
+
expect.stringContaining('"userId": "123"')
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("should print context from context field", () => {
|
|
205
|
+
const context = { userId: "456", feature: "login", source: "auth" };
|
|
206
|
+
const testLog: ILog = {
|
|
207
|
+
level: "info",
|
|
208
|
+
message: "User action",
|
|
209
|
+
context: context,
|
|
210
|
+
timestamp: new Date("2023-01-01T00:00:00Z"),
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
logger.print(testLog);
|
|
214
|
+
|
|
215
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
216
|
+
expect.stringContaining("INFO")
|
|
217
|
+
);
|
|
218
|
+
// Should show context but filter out 'source' since it's shown separately
|
|
219
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
220
|
+
expect.stringContaining("Context:")
|
|
221
|
+
);
|
|
222
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
223
|
+
expect.stringContaining('"userId": "456"')
|
|
224
|
+
);
|
|
225
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
226
|
+
expect.stringContaining('"feature": "login"')
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("should handle empty context gracefully", () => {
|
|
231
|
+
const testLog: ILog = {
|
|
232
|
+
level: "debug",
|
|
233
|
+
message: "Debug message",
|
|
234
|
+
context: {},
|
|
235
|
+
timestamp: new Date("2023-01-01T00:00:00Z"),
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
logger.print(testLog);
|
|
239
|
+
|
|
240
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
241
|
+
expect.stringContaining("DEBUG")
|
|
242
|
+
);
|
|
243
|
+
// Should not print context section for empty context
|
|
244
|
+
expect(consoleLogSpy).not.toHaveBeenCalledWith(
|
|
245
|
+
expect.stringContaining("Context:")
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("should handle unknown log levels gracefully", () => {
|
|
250
|
+
const testLog: ILog = {
|
|
251
|
+
level: "unknown" as any,
|
|
252
|
+
message: "Unknown level message",
|
|
253
|
+
timestamp: new Date("2023-01-01T00:00:00Z"),
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
logger.print(testLog);
|
|
115
257
|
|
|
116
258
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
117
|
-
expect.stringContaining(
|
|
259
|
+
expect.stringContaining("UNKNOWN")
|
|
118
260
|
);
|
|
119
261
|
});
|
|
120
262
|
});
|
|
@@ -130,21 +272,123 @@ describe("Logger", () => {
|
|
|
130
272
|
];
|
|
131
273
|
|
|
132
274
|
for (const level of testLevels) {
|
|
133
|
-
it(`should call log method with ${level} level`,
|
|
275
|
+
it(`should call log method with ${level} level`, () => {
|
|
134
276
|
const logSpy = jest.spyOn(logger, "log").mockImplementation();
|
|
135
277
|
|
|
136
|
-
|
|
278
|
+
logger[level]("Test log message");
|
|
137
279
|
|
|
138
280
|
expect(logSpy).toHaveBeenCalledWith(
|
|
139
281
|
level,
|
|
140
282
|
"Test log message",
|
|
141
|
-
|
|
283
|
+
{} // the LogInfo parameter
|
|
142
284
|
);
|
|
143
285
|
});
|
|
144
286
|
}
|
|
145
287
|
});
|
|
146
288
|
|
|
147
|
-
|
|
289
|
+
describe("with method", () => {
|
|
290
|
+
it("should create a new logger with additional context", () => {
|
|
291
|
+
const initialContext = { source: "initial" };
|
|
292
|
+
const loggerWithContext = new Logger(mockEventManager, initialContext);
|
|
293
|
+
|
|
294
|
+
const additionalContext = { userId: "123", feature: "auth" };
|
|
295
|
+
const newLogger = loggerWithContext.with(additionalContext);
|
|
296
|
+
|
|
297
|
+
expect(newLogger).toBeInstanceOf(Logger);
|
|
298
|
+
expect(newLogger).not.toBe(loggerWithContext);
|
|
299
|
+
|
|
300
|
+
// Test that the context is merged by checking the log call
|
|
301
|
+
const logSpy = jest.spyOn(newLogger, "log").mockImplementation();
|
|
302
|
+
newLogger.info("test message");
|
|
303
|
+
|
|
304
|
+
expect(logSpy).toHaveBeenCalledWith("info", "test message", {});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("should override context values when keys overlap", () => {
|
|
308
|
+
const initialContext = { source: "initial", common: "old" };
|
|
309
|
+
const loggerWithContext = new Logger(mockEventManager, initialContext);
|
|
310
|
+
|
|
311
|
+
const newContext = { source: "override", newProp: "value" };
|
|
312
|
+
const newLogger = loggerWithContext.with(newContext);
|
|
313
|
+
|
|
314
|
+
// The new logger should have the overridden context
|
|
315
|
+
expect(newLogger).toBeInstanceOf(Logger);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("should use bound context in log calls", async () => {
|
|
319
|
+
const boundContext = { source: "testSource", userId: "123" };
|
|
320
|
+
const loggerWithContext = new Logger(mockEventManager, boundContext);
|
|
321
|
+
|
|
322
|
+
mockEventManager.emit = jest.fn().mockResolvedValue(undefined);
|
|
323
|
+
mockEventManager.hasListeners = jest.fn().mockReturnValue(true);
|
|
324
|
+
|
|
325
|
+
loggerWithContext.log("info", "Test message");
|
|
326
|
+
|
|
327
|
+
await new Promise(setImmediate);
|
|
328
|
+
|
|
329
|
+
expect(mockEventManager.emit).toHaveBeenCalledWith(
|
|
330
|
+
globalEvents.log,
|
|
331
|
+
expect.objectContaining({
|
|
332
|
+
source: "testSource",
|
|
333
|
+
context: boundContext,
|
|
334
|
+
}),
|
|
335
|
+
"testSource"
|
|
336
|
+
);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe("error handling", () => {
|
|
341
|
+
it("should handle event emission errors gracefully", async () => {
|
|
342
|
+
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation();
|
|
343
|
+
const emitError = new Error("Event emission failed");
|
|
344
|
+
|
|
345
|
+
mockEventManager.emit = jest.fn().mockRejectedValue(emitError);
|
|
346
|
+
mockEventManager.hasListeners = jest.fn().mockReturnValue(true);
|
|
347
|
+
|
|
348
|
+
logger.log("error", "Test error message");
|
|
349
|
+
|
|
350
|
+
// Wait for setImmediate and promise rejection to be handled
|
|
351
|
+
await new Promise(setImmediate);
|
|
352
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
353
|
+
|
|
354
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
355
|
+
"Logger event emission failed:",
|
|
356
|
+
emitError
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
consoleErrorSpy.mockRestore();
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it("should extract error information from Error objects", async () => {
|
|
363
|
+
mockEventManager.emit = jest.fn().mockResolvedValue(undefined);
|
|
364
|
+
mockEventManager.hasListeners = jest.fn().mockReturnValue(true);
|
|
365
|
+
|
|
366
|
+
const testError = new Error("Test error message");
|
|
367
|
+
testError.name = "CustomError";
|
|
368
|
+
|
|
369
|
+
logger.log("error", "Operation failed", { error: testError });
|
|
370
|
+
|
|
371
|
+
// Wait for setImmediate to execute
|
|
372
|
+
await new Promise(setImmediate);
|
|
373
|
+
|
|
374
|
+
expect(mockEventManager.emit).toHaveBeenCalledWith(
|
|
375
|
+
globalEvents.log,
|
|
376
|
+
expect.objectContaining({
|
|
377
|
+
level: "error",
|
|
378
|
+
message: "Operation failed",
|
|
379
|
+
error: {
|
|
380
|
+
name: "CustomError",
|
|
381
|
+
message: "Test error message",
|
|
382
|
+
stack: expect.any(String),
|
|
383
|
+
},
|
|
384
|
+
timestamp: expect.any(Date),
|
|
385
|
+
}),
|
|
386
|
+
"unknown"
|
|
387
|
+
);
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it("should auto-print logs based on autoPrintLogsAfter option", () => {
|
|
148
392
|
const autoPrintLevel: LogLevels = "warn";
|
|
149
393
|
logger.setPrintThreshold(autoPrintLevel);
|
|
150
394
|
const consoleLogSpy = jest.spyOn(console, "log").mockImplementation();
|
|
@@ -160,7 +404,7 @@ describe("Logger", () => {
|
|
|
160
404
|
|
|
161
405
|
for (const level of levels) {
|
|
162
406
|
logger.setPrintThreshold(level);
|
|
163
|
-
|
|
407
|
+
logger.log(level, `Test ${level} message`);
|
|
164
408
|
|
|
165
409
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
166
410
|
expect.stringContaining(`Test ${level} message`)
|
|
@@ -169,8 +413,8 @@ describe("Logger", () => {
|
|
|
169
413
|
|
|
170
414
|
// ensure events with a higher level thatn auto print level are printed, and lower levels are not
|
|
171
415
|
logger.setPrintThreshold("error");
|
|
172
|
-
|
|
173
|
-
|
|
416
|
+
logger.log("info", "xx Test info message");
|
|
417
|
+
logger.log("error", "xx Test error message");
|
|
174
418
|
|
|
175
419
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
176
420
|
expect.stringContaining("xx Test error message")
|
|
@@ -182,4 +426,17 @@ describe("Logger", () => {
|
|
|
182
426
|
|
|
183
427
|
consoleLogSpy.mockRestore();
|
|
184
428
|
});
|
|
429
|
+
|
|
430
|
+
it("should disable auto-printing when setPrintThreshold is set to null", () => {
|
|
431
|
+
const consoleLogSpy = jest.spyOn(console, "log").mockImplementation();
|
|
432
|
+
|
|
433
|
+
// Set to null to disable auto-printing
|
|
434
|
+
logger.setPrintThreshold(null);
|
|
435
|
+
|
|
436
|
+
logger.log("error", "Should not be printed");
|
|
437
|
+
|
|
438
|
+
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
439
|
+
|
|
440
|
+
consoleLogSpy.mockRestore();
|
|
441
|
+
});
|
|
185
442
|
});
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// queue.test.ts
|
|
2
|
+
import { Queue } from "../.."; // <-- adjust path if needed
|
|
3
|
+
|
|
4
|
+
/* ------------------------------------------------------------------ */
|
|
5
|
+
/* Helpers */
|
|
6
|
+
/* ------------------------------------------------------------------ */
|
|
7
|
+
|
|
8
|
+
/** Flush native micro‑tasks once (Promise jobs / process.nextTick). */
|
|
9
|
+
const flushMicroTasks = () => Promise.resolve();
|
|
10
|
+
|
|
11
|
+
/** Small delay helper for tests */
|
|
12
|
+
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
13
|
+
|
|
14
|
+
/* ------------------------------------------------------------------ */
|
|
15
|
+
/* Tests */
|
|
16
|
+
/* ------------------------------------------------------------------ */
|
|
17
|
+
|
|
18
|
+
describe("Queue", () => {
|
|
19
|
+
it("runs tasks sequentially and returns their results in order", async () => {
|
|
20
|
+
const q = new Queue();
|
|
21
|
+
|
|
22
|
+
const started: number[] = [];
|
|
23
|
+
const finished: number[] = [];
|
|
24
|
+
let concurrent = 0;
|
|
25
|
+
let maxConcurrent = 0;
|
|
26
|
+
|
|
27
|
+
const taskFactory = (id: number) => async () => {
|
|
28
|
+
started.push(id);
|
|
29
|
+
concurrent++;
|
|
30
|
+
maxConcurrent = Math.max(maxConcurrent, concurrent);
|
|
31
|
+
|
|
32
|
+
// simulate async work with a real small delay
|
|
33
|
+
await delay(1);
|
|
34
|
+
|
|
35
|
+
finished.push(id);
|
|
36
|
+
concurrent--;
|
|
37
|
+
return id;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const p1 = q.run(taskFactory(1));
|
|
41
|
+
const p2 = q.run(taskFactory(2));
|
|
42
|
+
const p3 = q.run(taskFactory(3));
|
|
43
|
+
|
|
44
|
+
const results = await Promise.all([p1, p2, p3]);
|
|
45
|
+
|
|
46
|
+
expect(results).toEqual([1, 2, 3]);
|
|
47
|
+
expect(started).toEqual([1, 2, 3]);
|
|
48
|
+
expect(finished).toEqual([1, 2, 3]);
|
|
49
|
+
expect(maxConcurrent).toBe(1); // never overlapped
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("detects dead‑lock when a queued task enqueues another task", async () => {
|
|
53
|
+
const q = new Queue();
|
|
54
|
+
|
|
55
|
+
const deadlock = () => q.run(async () => "nested"); // <-- illegal
|
|
56
|
+
|
|
57
|
+
await expect(q.run(deadlock)).rejects.toThrow(/Dead‑lock/);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("dispose() drains pending tasks and rejects new ones", async () => {
|
|
61
|
+
const q = new Queue();
|
|
62
|
+
|
|
63
|
+
const task = async () => {
|
|
64
|
+
await delay(1);
|
|
65
|
+
return "ok";
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const p1 = q.run(task);
|
|
69
|
+
const p2 = q.run(task);
|
|
70
|
+
|
|
71
|
+
const disposeDone = q.dispose(); // default => { cancel: false }
|
|
72
|
+
|
|
73
|
+
await expect(disposeDone).resolves.toBeUndefined();
|
|
74
|
+
await expect(p1).resolves.toBe("ok");
|
|
75
|
+
await expect(p2).resolves.toBe("ok");
|
|
76
|
+
|
|
77
|
+
await expect(q.run(task)).rejects.toThrow(/disposed/);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("dispose({ cancel: true }) aborts the running task", async () => {
|
|
81
|
+
jest.useFakeTimers();
|
|
82
|
+
|
|
83
|
+
const q = new Queue();
|
|
84
|
+
|
|
85
|
+
/** Long‑running task that cooperates with AbortSignal. */
|
|
86
|
+
const longTask = async (signal: AbortSignal) =>
|
|
87
|
+
new Promise<void>((_res, rej) => {
|
|
88
|
+
const tid = setTimeout(_res, 100); // never actually fires
|
|
89
|
+
signal.addEventListener("abort", () => {
|
|
90
|
+
clearTimeout(tid);
|
|
91
|
+
rej(new Error("aborted"));
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const p = q.run(longTask);
|
|
96
|
+
|
|
97
|
+
// Let the task start
|
|
98
|
+
jest.advanceTimersByTime(0);
|
|
99
|
+
await flushMicroTasks();
|
|
100
|
+
|
|
101
|
+
const disposeDone = q.dispose({ cancel: true });
|
|
102
|
+
|
|
103
|
+
await flushMicroTasks(); // allow rejection to propagate
|
|
104
|
+
|
|
105
|
+
await expect(p).rejects.toThrow(/aborted/);
|
|
106
|
+
await expect(disposeDone).resolves.toBeUndefined();
|
|
107
|
+
|
|
108
|
+
jest.useRealTimers();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("dispose() is idempotent - multiple calls should be safe", async () => {
|
|
112
|
+
const q = new Queue();
|
|
113
|
+
|
|
114
|
+
const task = async () => {
|
|
115
|
+
await delay(1);
|
|
116
|
+
return "ok";
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const p = q.run(task);
|
|
120
|
+
|
|
121
|
+
// First dispose call
|
|
122
|
+
const dispose1 = q.dispose();
|
|
123
|
+
// Second dispose call should return immediately (if (this.disposed) return;)
|
|
124
|
+
const dispose2 = q.dispose();
|
|
125
|
+
// Third dispose call should also return immediately
|
|
126
|
+
const dispose3 = q.dispose();
|
|
127
|
+
|
|
128
|
+
await expect(dispose1).resolves.toBeUndefined();
|
|
129
|
+
await expect(dispose2).resolves.toBeUndefined();
|
|
130
|
+
await expect(dispose3).resolves.toBeUndefined();
|
|
131
|
+
await expect(p).resolves.toBe("ok");
|
|
132
|
+
|
|
133
|
+
// Further dispose calls after everything is settled should also be safe
|
|
134
|
+
const dispose4 = q.dispose();
|
|
135
|
+
await expect(dispose4).resolves.toBeUndefined();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("dispose() properly handles rejected tail promises", async () => {
|
|
139
|
+
const q = new Queue();
|
|
140
|
+
|
|
141
|
+
// Run a task first to establish the normal tail chain
|
|
142
|
+
await q.run(async () => "setup");
|
|
143
|
+
|
|
144
|
+
// Directly set the tail to a promise that will reject
|
|
145
|
+
// This simulates an internal error scenario where the tail becomes rejected
|
|
146
|
+
const rejectingPromise = Promise.reject(
|
|
147
|
+
new Error("Simulated tail rejection")
|
|
148
|
+
);
|
|
149
|
+
(q as any).tail = rejectingPromise;
|
|
150
|
+
|
|
151
|
+
// Spy on the rejecting promise to verify the catch is called
|
|
152
|
+
const catchSpy = jest.spyOn(rejectingPromise, "catch");
|
|
153
|
+
|
|
154
|
+
// The dispose method should handle this rejection with: await this.tail.catch(() => {})
|
|
155
|
+
// This specifically tests the line: await this.tail.catch(() => {});
|
|
156
|
+
await expect(q.dispose()).resolves.toBeUndefined();
|
|
157
|
+
|
|
158
|
+
// Verify that the catch method was called (meaning the tail.catch() line was executed)
|
|
159
|
+
expect(catchSpy).toHaveBeenCalledWith(expect.any(Function));
|
|
160
|
+
|
|
161
|
+
// Verify the queue is properly disposed
|
|
162
|
+
await expect(q.run(async () => "test")).rejects.toThrow(/disposed/);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("should propagate task exceptions to the caller", async () => {
|
|
166
|
+
const q = new Queue();
|
|
167
|
+
|
|
168
|
+
// Test that exceptions are propagated, not swallowed
|
|
169
|
+
const errorTask = async () => {
|
|
170
|
+
await delay(1);
|
|
171
|
+
throw new Error("Task exception");
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const successTask = async () => {
|
|
175
|
+
await delay(1);
|
|
176
|
+
return "success";
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Exception should be catchable by the caller
|
|
180
|
+
await expect(q.run(errorTask)).rejects.toThrow("Task exception");
|
|
181
|
+
|
|
182
|
+
// Queue should still work for subsequent tasks
|
|
183
|
+
await expect(q.run(successTask)).resolves.toBe("success");
|
|
184
|
+
|
|
185
|
+
// Multiple exceptions should all be catchable
|
|
186
|
+
await expect(q.run(errorTask)).rejects.toThrow("Task exception");
|
|
187
|
+
await expect(q.run(errorTask)).rejects.toThrow("Task exception");
|
|
188
|
+
});
|
|
189
|
+
});
|
|
@@ -3,8 +3,8 @@ import { Store } from "../../models/Store";
|
|
|
3
3
|
import { EventManager } from "../../models/EventManager";
|
|
4
4
|
import { defineResource } from "../../define";
|
|
5
5
|
import { Logger } from "../../models";
|
|
6
|
-
import { globalResources } from "../../globalResources";
|
|
7
|
-
import { globalEvents } from "../../globalEvents";
|
|
6
|
+
import { globalResources } from "../../globals/globalResources";
|
|
7
|
+
import { globalEvents } from "../../globals/globalEvents";
|
|
8
8
|
|
|
9
9
|
describe("ResourceInitializer", () => {
|
|
10
10
|
let store: Store;
|
|
@@ -36,10 +36,11 @@ describe("ResourceInitializer", () => {
|
|
|
36
36
|
mockDependencies
|
|
37
37
|
);
|
|
38
38
|
|
|
39
|
-
expect(result).
|
|
39
|
+
expect(result).toEqual({ value: "initialized value", context: undefined });
|
|
40
40
|
expect(mockResource.init).toHaveBeenCalledWith(
|
|
41
41
|
mockConfig,
|
|
42
|
-
mockDependencies
|
|
42
|
+
mockDependencies,
|
|
43
|
+
undefined
|
|
43
44
|
);
|
|
44
45
|
|
|
45
46
|
expect(emitSpy).toHaveBeenCalledWith(
|
|
@@ -91,7 +92,8 @@ describe("ResourceInitializer", () => {
|
|
|
91
92
|
expect(result).toBeUndefined();
|
|
92
93
|
expect(mockResource.init).toHaveBeenCalledWith(
|
|
93
94
|
mockConfig,
|
|
94
|
-
mockDependencies
|
|
95
|
+
mockDependencies,
|
|
96
|
+
undefined
|
|
95
97
|
);
|
|
96
98
|
expect(emitSpy).toHaveBeenCalledWith(
|
|
97
99
|
mockResource.events.beforeInit,
|
|
@@ -126,7 +128,7 @@ describe("ResourceInitializer", () => {
|
|
|
126
128
|
mockDependencies
|
|
127
129
|
);
|
|
128
130
|
|
|
129
|
-
expect(result).
|
|
131
|
+
expect(result).toEqual({ value: undefined, context: undefined });
|
|
130
132
|
expect(emitSpy).toHaveBeenCalledWith(
|
|
131
133
|
mockResource.events.beforeInit,
|
|
132
134
|
{
|