@buenojs/bueno 0.8.4 → 0.8.6
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 +264 -17
- package/dist/cli/{index.js → bin.js} +413 -332
- package/dist/container/index.js +273 -0
- package/dist/context/index.js +219 -0
- package/dist/database/index.js +493 -0
- package/dist/frontend/index.js +7697 -0
- package/dist/graphql/index.js +2156 -0
- package/dist/health/index.js +364 -0
- package/dist/i18n/index.js +345 -0
- package/dist/index.js +9694 -5047
- package/dist/jobs/index.js +819 -0
- package/dist/lock/index.js +367 -0
- package/dist/logger/index.js +281 -0
- package/dist/metrics/index.js +289 -0
- package/dist/middleware/index.js +77 -0
- package/dist/migrations/index.js +571 -0
- package/dist/modules/index.js +3411 -0
- package/dist/notification/index.js +484 -0
- package/dist/observability/index.js +331 -0
- package/dist/openapi/index.js +795 -0
- package/dist/orm/index.js +1356 -0
- package/dist/router/index.js +886 -0
- package/dist/rpc/index.js +691 -0
- package/dist/schema/index.js +400 -0
- package/dist/telemetry/index.js +595 -0
- package/dist/template/index.js +640 -0
- package/dist/templates/index.js +640 -0
- package/dist/testing/index.js +1111 -0
- package/dist/types/index.js +60 -0
- package/llms.txt +231 -0
- package/package.json +125 -27
- package/src/cache/index.ts +2 -1
- package/src/cli/ARCHITECTURE.md +3 -3
- package/src/cli/bin.ts +2 -2
- package/src/cli/commands/build.ts +183 -165
- package/src/cli/commands/dev.ts +96 -89
- package/src/cli/commands/generate.ts +142 -111
- package/src/cli/commands/help.ts +20 -16
- package/src/cli/commands/index.ts +3 -6
- package/src/cli/commands/migration.ts +124 -105
- package/src/cli/commands/new.ts +294 -232
- package/src/cli/commands/start.ts +81 -79
- package/src/cli/core/args.ts +68 -50
- package/src/cli/core/console.ts +89 -95
- package/src/cli/core/index.ts +4 -4
- package/src/cli/core/prompt.ts +65 -62
- package/src/cli/core/spinner.ts +23 -20
- package/src/cli/index.ts +46 -38
- package/src/cli/templates/database/index.ts +37 -18
- package/src/cli/templates/database/mysql.ts +3 -3
- package/src/cli/templates/database/none.ts +2 -2
- package/src/cli/templates/database/postgresql.ts +3 -3
- package/src/cli/templates/database/sqlite.ts +3 -3
- package/src/cli/templates/deploy.ts +29 -26
- package/src/cli/templates/docker.ts +41 -30
- package/src/cli/templates/frontend/index.ts +33 -15
- package/src/cli/templates/frontend/none.ts +2 -2
- package/src/cli/templates/frontend/react.ts +18 -18
- package/src/cli/templates/frontend/solid.ts +15 -15
- package/src/cli/templates/frontend/svelte.ts +17 -17
- package/src/cli/templates/frontend/vue.ts +15 -15
- package/src/cli/templates/generators/index.ts +29 -29
- package/src/cli/templates/generators/types.ts +21 -21
- package/src/cli/templates/index.ts +6 -6
- package/src/cli/templates/project/api.ts +37 -36
- package/src/cli/templates/project/default.ts +25 -25
- package/src/cli/templates/project/fullstack.ts +28 -26
- package/src/cli/templates/project/index.ts +55 -16
- package/src/cli/templates/project/minimal.ts +17 -12
- package/src/cli/templates/project/types.ts +10 -5
- package/src/cli/templates/project/website.ts +15 -15
- package/src/cli/utils/fs.ts +55 -41
- package/src/cli/utils/index.ts +3 -3
- package/src/cli/utils/strings.ts +47 -33
- package/src/cli/utils/version.ts +14 -8
- package/src/config/env-validation.ts +100 -0
- package/src/config/env.ts +169 -41
- package/src/config/index.ts +28 -20
- package/src/config/loader.ts +25 -16
- package/src/config/merge.ts +21 -10
- package/src/config/types.ts +566 -25
- package/src/config/validation.ts +215 -7
- package/src/container/forward-ref.ts +22 -22
- package/src/container/index.ts +34 -12
- package/src/context/index.ts +11 -1
- package/src/database/index.ts +7 -190
- package/src/database/orm/builder.ts +457 -0
- package/src/database/orm/casts/index.ts +130 -0
- package/src/database/orm/casts/types.ts +25 -0
- package/src/database/orm/compiler.ts +304 -0
- package/src/database/orm/hooks/index.ts +114 -0
- package/src/database/orm/index.ts +61 -0
- package/src/database/orm/model-registry.ts +59 -0
- package/src/database/orm/model.ts +821 -0
- package/src/database/orm/relationships/base.ts +146 -0
- package/src/database/orm/relationships/belongs-to-many.ts +179 -0
- package/src/database/orm/relationships/belongs-to.ts +56 -0
- package/src/database/orm/relationships/has-many.ts +45 -0
- package/src/database/orm/relationships/has-one.ts +41 -0
- package/src/database/orm/relationships/index.ts +11 -0
- package/src/database/orm/scopes/index.ts +55 -0
- package/src/events/__tests__/event-system.test.ts +235 -0
- package/src/events/config.ts +238 -0
- package/src/events/example-usage.ts +185 -0
- package/src/events/index.ts +278 -0
- package/src/events/manager.ts +385 -0
- package/src/events/registry.ts +182 -0
- package/src/events/types.ts +124 -0
- package/src/frontend/api-routes.ts +65 -23
- package/src/frontend/bundler.ts +76 -34
- package/src/frontend/console-client.ts +2 -2
- package/src/frontend/console-stream.ts +94 -38
- package/src/frontend/dev-server.ts +94 -46
- package/src/frontend/file-router.ts +61 -19
- package/src/frontend/frameworks/index.ts +37 -10
- package/src/frontend/frameworks/react.ts +10 -8
- package/src/frontend/frameworks/solid.ts +11 -9
- package/src/frontend/frameworks/svelte.ts +15 -9
- package/src/frontend/frameworks/vue.ts +13 -11
- package/src/frontend/hmr-client.ts +12 -10
- package/src/frontend/hmr.ts +146 -103
- package/src/frontend/index.ts +14 -5
- package/src/frontend/islands.ts +41 -22
- package/src/frontend/isr.ts +59 -37
- package/src/frontend/layout.ts +36 -21
- package/src/frontend/ssr/react.ts +74 -27
- package/src/frontend/ssr/solid.ts +54 -20
- package/src/frontend/ssr/svelte.ts +48 -14
- package/src/frontend/ssr/vue.ts +50 -18
- package/src/frontend/ssr.ts +83 -39
- package/src/frontend/types.ts +91 -56
- package/src/graphql/built-in-engine.ts +598 -0
- package/src/graphql/context-builder.ts +110 -0
- package/src/graphql/decorators.ts +358 -0
- package/src/graphql/execution-pipeline.ts +227 -0
- package/src/graphql/graphql-module.ts +563 -0
- package/src/graphql/index.ts +101 -0
- package/src/graphql/metadata.ts +237 -0
- package/src/graphql/schema-builder.ts +319 -0
- package/src/graphql/subscription-handler.ts +283 -0
- package/src/graphql/types.ts +324 -0
- package/src/health/index.ts +21 -9
- package/src/i18n/engine.ts +305 -0
- package/src/i18n/index.ts +38 -0
- package/src/i18n/loader.ts +218 -0
- package/src/i18n/middleware.ts +164 -0
- package/src/i18n/negotiator.ts +162 -0
- package/src/i18n/types.ts +158 -0
- package/src/index.ts +182 -27
- package/src/jobs/drivers/memory.ts +315 -0
- package/src/jobs/drivers/redis.ts +459 -0
- package/src/jobs/index.ts +30 -0
- package/src/jobs/queue.ts +281 -0
- package/src/jobs/types.ts +295 -0
- package/src/jobs/worker.ts +380 -0
- package/src/logger/index.ts +1 -3
- package/src/logger/transports/index.ts +62 -22
- package/src/metrics/index.ts +25 -16
- package/src/migrations/index.ts +9 -0
- package/src/modules/filters.ts +13 -17
- package/src/modules/guards.ts +49 -26
- package/src/modules/index.ts +457 -299
- package/src/modules/interceptors.ts +58 -20
- package/src/modules/lazy.ts +11 -19
- package/src/modules/lifecycle.ts +15 -7
- package/src/modules/metadata.ts +15 -5
- package/src/modules/pipes.ts +94 -72
- package/src/notification/channels/base.ts +68 -0
- package/src/notification/channels/email.ts +105 -0
- package/src/notification/channels/push.ts +104 -0
- package/src/notification/channels/sms.ts +105 -0
- package/src/notification/channels/whatsapp.ts +104 -0
- package/src/notification/index.ts +48 -0
- package/src/notification/service.ts +354 -0
- package/src/notification/types.ts +344 -0
- package/src/observability/__tests__/observability.test.ts +483 -0
- package/src/observability/breadcrumbs.ts +114 -0
- package/src/observability/index.ts +136 -0
- package/src/observability/interceptor.ts +85 -0
- package/src/observability/service.ts +303 -0
- package/src/observability/trace.ts +37 -0
- package/src/observability/types.ts +196 -0
- package/src/openapi/__tests__/decorators.test.ts +335 -0
- package/src/openapi/__tests__/document-builder.test.ts +285 -0
- package/src/openapi/__tests__/route-scanner.test.ts +334 -0
- package/src/openapi/__tests__/schema-generator.test.ts +275 -0
- package/src/openapi/decorators.ts +328 -0
- package/src/openapi/document-builder.ts +274 -0
- package/src/openapi/index.ts +112 -0
- package/src/openapi/metadata.ts +112 -0
- package/src/openapi/route-scanner.ts +289 -0
- package/src/openapi/schema-generator.ts +256 -0
- package/src/openapi/swagger-module.ts +166 -0
- package/src/openapi/types.ts +398 -0
- package/src/orm/index.ts +10 -0
- package/src/rpc/index.ts +3 -1
- package/src/schema/index.ts +9 -0
- package/src/security/index.ts +15 -6
- package/src/ssg/index.ts +9 -8
- package/src/telemetry/index.ts +76 -22
- package/src/template/index.ts +7 -0
- package/src/templates/engine.ts +224 -0
- package/src/templates/index.ts +9 -0
- package/src/templates/loader.ts +331 -0
- package/src/templates/renderers/markdown.ts +212 -0
- package/src/templates/renderers/simple.ts +269 -0
- package/src/templates/types.ts +154 -0
- package/src/testing/index.ts +100 -27
- package/src/types/optional-deps.d.ts +347 -187
- package/src/validation/index.ts +92 -2
- package/src/validation/schemas.ts +536 -0
- package/tests/integration/cli.test.ts +19 -19
- package/tests/integration/fullstack.test.ts +4 -4
- package/tests/unit/cli.test.ts +1 -1
- package/tests/unit/database.test.ts +2 -72
- package/tests/unit/env-validation.test.ts +166 -0
- package/tests/unit/events.test.ts +910 -0
- package/tests/unit/graphql.test.ts +991 -0
- package/tests/unit/i18n.test.ts +455 -0
- package/tests/unit/jobs.test.ts +493 -0
- package/tests/unit/notification.test.ts +988 -0
- package/tests/unit/observability.test.ts +453 -0
- package/tests/unit/orm/builder.test.ts +323 -0
- package/tests/unit/orm/casts.test.ts +179 -0
- package/tests/unit/orm/compiler.test.ts +220 -0
- package/tests/unit/orm/eager-loading.test.ts +285 -0
- package/tests/unit/orm/hooks.test.ts +191 -0
- package/tests/unit/orm/model.test.ts +373 -0
- package/tests/unit/orm/relationships.test.ts +303 -0
- package/tests/unit/orm/scopes.test.ts +74 -0
- package/tests/unit/templates-simple.test.ts +53 -0
- package/tests/unit/templates.test.ts +454 -0
- package/tests/unit/validation.test.ts +18 -24
- package/tsconfig.json +11 -3
|
@@ -0,0 +1,910 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createEventManager,
|
|
3
|
+
createEventRegistry,
|
|
4
|
+
createEvent,
|
|
5
|
+
createEventListener,
|
|
6
|
+
createEventCategory,
|
|
7
|
+
createEventContext,
|
|
8
|
+
createEventMiddleware,
|
|
9
|
+
createEventFilter,
|
|
10
|
+
validateEvent,
|
|
11
|
+
serializeEvent,
|
|
12
|
+
deserializeEvent,
|
|
13
|
+
createEventError,
|
|
14
|
+
createEventThrottler,
|
|
15
|
+
sortListenersByPriority,
|
|
16
|
+
mergeEventContexts,
|
|
17
|
+
calculateEventThroughput,
|
|
18
|
+
createCategoryFilter,
|
|
19
|
+
createNameFilter,
|
|
20
|
+
transformEventData,
|
|
21
|
+
cloneEvent,
|
|
22
|
+
areEventsEqual
|
|
23
|
+
} from '../../src/events/index';
|
|
24
|
+
import { Event, EventListener, EventContext, EventCategory, EventError } from '../../src/events/types';
|
|
25
|
+
|
|
26
|
+
describe('Event System - Comprehensive Test Suite', () => {
|
|
27
|
+
let manager: any;
|
|
28
|
+
let registry: any;
|
|
29
|
+
let defaultContext: EventContext;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
manager = createEventManager();
|
|
33
|
+
registry = createEventRegistry();
|
|
34
|
+
defaultContext = createEventContext({
|
|
35
|
+
userId: 'test-user',
|
|
36
|
+
sessionId: 'test-session',
|
|
37
|
+
ipAddress: '127.0.0.1'
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('Event Creation and Validation', () => {
|
|
42
|
+
it('should create valid events with all required properties', () => {
|
|
43
|
+
const eventData = { message: 'test' };
|
|
44
|
+
const event = createEvent('test.event', eventData, { context: defaultContext });
|
|
45
|
+
|
|
46
|
+
expect(event).toHaveProperty('id');
|
|
47
|
+
expect(event).toHaveProperty('name', 'test.event');
|
|
48
|
+
expect(event).toHaveProperty('timestamp');
|
|
49
|
+
expect(event).toHaveProperty('data', eventData);
|
|
50
|
+
expect(event).toHaveProperty('context', defaultContext);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should create events with custom timestamp', () => {
|
|
54
|
+
const customTimestamp = new Date('2023-01-01T00:00:00Z');
|
|
55
|
+
const event = createEvent('test.event', { message: 'test' }, { timestamp: customTimestamp });
|
|
56
|
+
|
|
57
|
+
expect(event.timestamp).toEqual(customTimestamp);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should create events with custom ID', () => {
|
|
61
|
+
const customId = 'custom-id-123';
|
|
62
|
+
const event = createEvent('test.event', { message: 'test' }, { id: customId });
|
|
63
|
+
|
|
64
|
+
expect(event.id).toBe(customId);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should validate events correctly', () => {
|
|
68
|
+
const validEvent = createEvent('test.event', { message: 'test' });
|
|
69
|
+
const invalidEvent = { name: 'test', data: {} };
|
|
70
|
+
|
|
71
|
+
expect(validateEvent(validEvent)).toBe(true);
|
|
72
|
+
expect(validateEvent(invalidEvent)).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should serialize and deserialize events correctly', () => {
|
|
76
|
+
const eventData = { message: 'test' };
|
|
77
|
+
const event = createEvent('test.event', eventData, { context: defaultContext });
|
|
78
|
+
const serialized = serializeEvent(event);
|
|
79
|
+
const deserialized = deserializeEvent(serialized);
|
|
80
|
+
|
|
81
|
+
expect(deserialized).toEqual(event);
|
|
82
|
+
expect(deserialized.timestamp).toBeInstanceOf(Date);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should create event errors correctly', () => {
|
|
86
|
+
const event = createEvent('test.event', { message: 'test' });
|
|
87
|
+
const error = createEventError(event, 'Test error', new Error('Original error'));
|
|
88
|
+
|
|
89
|
+
expect(error).toBeInstanceOf(Error);
|
|
90
|
+
expect(error.event).toEqual(event);
|
|
91
|
+
expect(error.originalError).toBeInstanceOf(Error);
|
|
92
|
+
expect(error.message).toBe('Test error');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('Event Manager - Core Functionality', () => {
|
|
97
|
+
it('should emit and handle events with multiple listeners', async () => {
|
|
98
|
+
const eventData = { message: 'test' };
|
|
99
|
+
const event = createEvent('test.event', eventData);
|
|
100
|
+
|
|
101
|
+
const handler1 = jest.fn();
|
|
102
|
+
const handler2 = jest.fn();
|
|
103
|
+
manager.on('test.event', handler1);
|
|
104
|
+
manager.on('test.event', handler2);
|
|
105
|
+
|
|
106
|
+
await manager.emit(event);
|
|
107
|
+
|
|
108
|
+
expect(handler1).toHaveBeenCalledWith(event);
|
|
109
|
+
expect(handler2).toHaveBeenCalledWith(event);
|
|
110
|
+
expect(handler1).toHaveBeenCalledTimes(1);
|
|
111
|
+
expect(handler2).toHaveBeenCalledTimes(1);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should support one-time listeners', async () => {
|
|
115
|
+
const eventData = { message: 'test' };
|
|
116
|
+
const event = createEvent('test.event', eventData);
|
|
117
|
+
|
|
118
|
+
const handler = jest.fn();
|
|
119
|
+
manager.once('test.event', handler);
|
|
120
|
+
|
|
121
|
+
await manager.emit(event);
|
|
122
|
+
await manager.emit(event);
|
|
123
|
+
|
|
124
|
+
expect(handler).toHaveBeenCalledWith(event);
|
|
125
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should support event filtering', async () => {
|
|
129
|
+
const eventData1 = { type: 'important' };
|
|
130
|
+
const eventData2 = { type: 'normal' };
|
|
131
|
+
const event1 = createEvent('test.event', eventData1);
|
|
132
|
+
const event2 = createEvent('test.event', eventData2);
|
|
133
|
+
|
|
134
|
+
const handler = jest.fn();
|
|
135
|
+
const filter = (e: any) => e.data.type === 'important';
|
|
136
|
+
manager.on('test.event', handler, { filter });
|
|
137
|
+
|
|
138
|
+
await manager.emit(event1);
|
|
139
|
+
await manager.emit(event2);
|
|
140
|
+
|
|
141
|
+
expect(handler).toHaveBeenCalledWith(event1);
|
|
142
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should support event middleware', async () => {
|
|
146
|
+
const eventData = { message: 'test' };
|
|
147
|
+
const event = createEvent('test.event', eventData);
|
|
148
|
+
|
|
149
|
+
const middleware = jest.fn(async (e, next) => {
|
|
150
|
+
e.data.middleware = true;
|
|
151
|
+
await next();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
manager.addMiddleware(middleware);
|
|
155
|
+
const handler = jest.fn();
|
|
156
|
+
manager.on('test.event', handler);
|
|
157
|
+
|
|
158
|
+
await manager.emit(event);
|
|
159
|
+
|
|
160
|
+
expect(middleware).toHaveBeenCalled();
|
|
161
|
+
expect(handler).toHaveBeenCalledWith(event);
|
|
162
|
+
expect(event.data.middleware).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should handle errors gracefully in listeners', async () => {
|
|
166
|
+
const eventData = { message: 'test' };
|
|
167
|
+
const event = createEvent('test.event', eventData);
|
|
168
|
+
|
|
169
|
+
const failingHandler = jest.fn(() => {
|
|
170
|
+
throw new Error('Test error');
|
|
171
|
+
});
|
|
172
|
+
const successHandler = jest.fn();
|
|
173
|
+
|
|
174
|
+
manager.on('test.event', failingHandler);
|
|
175
|
+
manager.on('test.event', successHandler);
|
|
176
|
+
|
|
177
|
+
await manager.emit(event);
|
|
178
|
+
|
|
179
|
+
expect(failingHandler).toHaveBeenCalled();
|
|
180
|
+
expect(successHandler).toHaveBeenCalled();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should handle errors gracefully in middleware', async () => {
|
|
184
|
+
const eventData = { message: 'test' };
|
|
185
|
+
const event = createEvent('test.event', eventData);
|
|
186
|
+
|
|
187
|
+
const failingMiddleware = jest.fn(async (e, next) => {
|
|
188
|
+
throw new Error('Middleware error');
|
|
189
|
+
});
|
|
190
|
+
const successHandler = jest.fn();
|
|
191
|
+
|
|
192
|
+
manager.addMiddleware(failingMiddleware);
|
|
193
|
+
manager.on('test.event', successHandler);
|
|
194
|
+
|
|
195
|
+
await manager.emit(event);
|
|
196
|
+
|
|
197
|
+
expect(failingMiddleware).toHaveBeenCalled();
|
|
198
|
+
expect(successHandler).not.toHaveBeenCalled();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should support event categories', () => {
|
|
202
|
+
const category = createEventCategory('database', 'Database operations', ['query', 'mutation']);
|
|
203
|
+
manager.registerEventCategory(category);
|
|
204
|
+
|
|
205
|
+
expect(manager.getEventCategories()).toContainEqual(category);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should support removing listeners', async () => {
|
|
209
|
+
const eventData = { message: 'test' };
|
|
210
|
+
const event = createEvent('test.event', eventData);
|
|
211
|
+
|
|
212
|
+
const handler = jest.fn();
|
|
213
|
+
const removeListener = manager.on('test.event', handler);
|
|
214
|
+
|
|
215
|
+
removeListener();
|
|
216
|
+
await manager.emit(event);
|
|
217
|
+
|
|
218
|
+
expect(handler).not.toHaveBeenCalled();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should support removing middleware', async () => {
|
|
222
|
+
const eventData = { message: 'test' };
|
|
223
|
+
const event = createEvent('test.event', eventData);
|
|
224
|
+
|
|
225
|
+
const middleware = jest.fn(async (e, next) => {
|
|
226
|
+
e.data.middleware = true;
|
|
227
|
+
await next();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
manager.addMiddleware(middleware);
|
|
231
|
+
manager.removeMiddleware(middleware);
|
|
232
|
+
|
|
233
|
+
const handler = jest.fn();
|
|
234
|
+
manager.on('test.event', handler);
|
|
235
|
+
|
|
236
|
+
await manager.emit(event);
|
|
237
|
+
|
|
238
|
+
expect(middleware).not.toHaveBeenCalled();
|
|
239
|
+
expect(handler).toHaveBeenCalledWith(event);
|
|
240
|
+
expect(event.data.middleware).toBeUndefined();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should support removing filters', async () => {
|
|
244
|
+
const eventData = { type: 'important' };
|
|
245
|
+
const event = createEvent('test.event', eventData);
|
|
246
|
+
|
|
247
|
+
const filter = (e: any) => e.data.type === 'important';
|
|
248
|
+
const handler = jest.fn();
|
|
249
|
+
|
|
250
|
+
manager.addFilter(filter);
|
|
251
|
+
manager.on('test.event', handler);
|
|
252
|
+
manager.removeFilter(filter);
|
|
253
|
+
|
|
254
|
+
await manager.emit(event);
|
|
255
|
+
|
|
256
|
+
expect(handler).toHaveBeenCalled();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should support clearing all listeners', async () => {
|
|
260
|
+
const eventData = { message: 'test' };
|
|
261
|
+
const event = createEvent('test.event', eventData);
|
|
262
|
+
|
|
263
|
+
const handler1 = jest.fn();
|
|
264
|
+
const handler2 = jest.fn();
|
|
265
|
+
manager.on('test.event', handler1);
|
|
266
|
+
manager.on('test.event', handler2);
|
|
267
|
+
|
|
268
|
+
manager.clearListeners();
|
|
269
|
+
await manager.emit(event);
|
|
270
|
+
|
|
271
|
+
expect(handler1).not.toHaveBeenCalled();
|
|
272
|
+
expect(handler2).not.toHaveBeenCalled();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should support clearing listeners for specific event', async () => {
|
|
276
|
+
const eventData = { message: 'test' };
|
|
277
|
+
const event1 = createEvent('test.event1', eventData);
|
|
278
|
+
const event2 = createEvent('test.event2', eventData);
|
|
279
|
+
|
|
280
|
+
const handler1 = jest.fn();
|
|
281
|
+
const handler2 = jest.fn();
|
|
282
|
+
manager.on('test.event1', handler1);
|
|
283
|
+
manager.on('test.event2', handler2);
|
|
284
|
+
|
|
285
|
+
manager.clearListeners('test.event1');
|
|
286
|
+
await manager.emit(event1);
|
|
287
|
+
await manager.emit(event2);
|
|
288
|
+
|
|
289
|
+
expect(handler1).not.toHaveBeenCalled();
|
|
290
|
+
expect(handler2).toHaveBeenCalled();
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe('Event Manager - Advanced Features', () => {
|
|
295
|
+
it('should support event throttling', async () => {
|
|
296
|
+
const throttler = createEventThrottler(2, 100);
|
|
297
|
+
const eventData = { message: 'test' };
|
|
298
|
+
const event = createEvent('test.event', eventData);
|
|
299
|
+
|
|
300
|
+
const handler = jest.fn();
|
|
301
|
+
manager.on('test.event', handler);
|
|
302
|
+
|
|
303
|
+
const shouldProcess1 = throttler(event);
|
|
304
|
+
const shouldProcess2 = throttler(event);
|
|
305
|
+
const shouldProcess3 = throttler(event);
|
|
306
|
+
|
|
307
|
+
if (shouldProcess1) await manager.emit(event);
|
|
308
|
+
if (shouldProcess2) await manager.emit(event);
|
|
309
|
+
if (shouldProcess3) await manager.emit(event);
|
|
310
|
+
|
|
311
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('should support event debouncing', async () => {
|
|
315
|
+
const debouncer = manager.createEventDebouncer(50);
|
|
316
|
+
const eventData = { message: 'test' };
|
|
317
|
+
const event = createEvent('test.event', eventData);
|
|
318
|
+
|
|
319
|
+
const handler = jest.fn();
|
|
320
|
+
manager.on('test.event', handler);
|
|
321
|
+
|
|
322
|
+
debouncer(event);
|
|
323
|
+
debouncer(event);
|
|
324
|
+
debouncer(event);
|
|
325
|
+
|
|
326
|
+
// Debouncer should process events after the wait time
|
|
327
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
328
|
+
|
|
329
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('should sort listeners by priority', () => {
|
|
333
|
+
const handlerLow = jest.fn();
|
|
334
|
+
const handlerHigh = jest.fn();
|
|
335
|
+
const listenerLow = createEventListener('test.event', handlerLow, { priority: 1 });
|
|
336
|
+
const listenerHigh = createEventListener('test.event', handlerHigh, { priority: 10 });
|
|
337
|
+
|
|
338
|
+
const sorted = sortListenersByPriority([listenerLow, listenerHigh]);
|
|
339
|
+
|
|
340
|
+
expect(sorted[0]).toBe(listenerHigh);
|
|
341
|
+
expect(sorted[1]).toBe(listenerLow);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('should merge event contexts correctly', () => {
|
|
345
|
+
const baseContext = createEventContext({ userId: 'user1' });
|
|
346
|
+
const additionalContext = { sessionId: 'session1', ipAddress: '192.168.1.1' };
|
|
347
|
+
const mergedContext = mergeEventContexts(baseContext, additionalContext);
|
|
348
|
+
|
|
349
|
+
expect(mergedContext).toHaveProperty('userId', 'user1');
|
|
350
|
+
expect(mergedContext).toHaveProperty('sessionId', 'session1');
|
|
351
|
+
expect(mergedContext).toHaveProperty('ipAddress', '192.168.1.1');
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('should calculate event throughput correctly', () => {
|
|
355
|
+
const events = [
|
|
356
|
+
createEvent('test.event', { count: 1 }),
|
|
357
|
+
createEvent('test.event', { count: 2 }),
|
|
358
|
+
createEvent('test.event', { count: 3 })
|
|
359
|
+
];
|
|
360
|
+
const throughput = calculateEventThroughput(events, 1000);
|
|
361
|
+
|
|
362
|
+
expect(throughput).toBe(3);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('should create category filters correctly', () => {
|
|
366
|
+
const categoryFilter = createCategoryFilter('database');
|
|
367
|
+
const event = createEvent('test.event', { message: 'test' }, { context: { category: 'database' } });
|
|
368
|
+
|
|
369
|
+
expect(categoryFilter(event)).toBe(true);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('should create name filters correctly', () => {
|
|
373
|
+
const nameFilter = createNameFilter('test.event');
|
|
374
|
+
const event = createEvent('test.event', { message: 'test' });
|
|
375
|
+
|
|
376
|
+
expect(nameFilter(event)).toBe(true);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('should transform event data correctly', () => {
|
|
380
|
+
const event = createEvent('test.event', { value: 10 });
|
|
381
|
+
const transformedEvent = transformEventData(event, (data: any) => ({ value: data.value * 2 }));
|
|
382
|
+
|
|
383
|
+
expect(transformedEvent.data.value).toBe(20);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('should clone events correctly', () => {
|
|
387
|
+
const event = createEvent('test.event', { message: 'test' });
|
|
388
|
+
const clonedEvent = cloneEvent(event);
|
|
389
|
+
|
|
390
|
+
expect(clonedEvent).toEqual(event);
|
|
391
|
+
expect(clonedEvent).not.toBe(event);
|
|
392
|
+
expect(clonedEvent.timestamp).not.toBe(event.timestamp);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('should compare events correctly', () => {
|
|
396
|
+
const event1 = createEvent('test.event', { message: 'test' });
|
|
397
|
+
const event2 = createEvent('test.event', { message: 'test' });
|
|
398
|
+
|
|
399
|
+
// Events should be equal except for the ID
|
|
400
|
+
expect(event1.name).toBe(event2.name);
|
|
401
|
+
expect(event1.timestamp.getTime()).toBe(event2.timestamp.getTime());
|
|
402
|
+
expect(event1.data).toEqual(event2.data);
|
|
403
|
+
expect(event1.context).toEqual(event2.context);
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
describe('Event Registry', () => {
|
|
408
|
+
it('should register and retrieve events', () => {
|
|
409
|
+
const eventData = { message: 'test' };
|
|
410
|
+
const event = createEvent('test.event', eventData);
|
|
411
|
+
|
|
412
|
+
registry.registerEvent(event);
|
|
413
|
+
|
|
414
|
+
expect(registry.getEvent(event.id)).toEqual(event);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it('should support event cleanup based on retention period', () => {
|
|
418
|
+
const eventData = { message: 'test' };
|
|
419
|
+
const event = createEvent('test.event', eventData, {
|
|
420
|
+
timestamp: new Date(Date.now() - 25 * 60 * 60 * 1000) // 25 hours ago
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
registry.registerEvent(event);
|
|
424
|
+
registry.cleanupOldEvents();
|
|
425
|
+
|
|
426
|
+
expect(registry.getEvent(event.id)).toBeUndefined();
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('should support event search', () => {
|
|
430
|
+
const event1 = createEvent('user.created', { name: 'John' });
|
|
431
|
+
const event2 = createEvent('user.deleted', { name: 'Jane' });
|
|
432
|
+
|
|
433
|
+
registry.registerEvent(event1);
|
|
434
|
+
registry.registerEvent(event2);
|
|
435
|
+
|
|
436
|
+
const results = registry.searchEvents('user');
|
|
437
|
+
|
|
438
|
+
expect(results).toHaveLength(2);
|
|
439
|
+
expect(results).toContainEqual(event1);
|
|
440
|
+
expect(results).toContainEqual(event2);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it('should support event history with filters', () => {
|
|
444
|
+
const events = [
|
|
445
|
+
createEvent('test.event', { count: 1 }, { timestamp: new Date(Date.now() - 2000) }),
|
|
446
|
+
createEvent('test.event', { count: 2 }, { timestamp: new Date(Date.now() - 1000) }),
|
|
447
|
+
createEvent('test.event', { count: 3 }, { timestamp: new Date() })
|
|
448
|
+
];
|
|
449
|
+
|
|
450
|
+
events.forEach(event => registry.registerEvent(event));
|
|
451
|
+
|
|
452
|
+
const history = registry.getEventHistory({
|
|
453
|
+
category: undefined,
|
|
454
|
+
startTime: new Date(Date.now() - 60 * 60 * 1000),
|
|
455
|
+
endTime: new Date(),
|
|
456
|
+
limit: 2
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
expect(history).toHaveLength(2);
|
|
460
|
+
expect(history[0].data.count).toBe(3);
|
|
461
|
+
expect(history[1].data.count).toBe(2);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('should support event timeline generation', () => {
|
|
465
|
+
const events = [
|
|
466
|
+
createEvent('test.event', { count: 1 }),
|
|
467
|
+
createEvent('test.event', { count: 2 }),
|
|
468
|
+
createEvent('test.event', { count: 3 })
|
|
469
|
+
];
|
|
470
|
+
|
|
471
|
+
events.forEach(event => registry.registerEvent(event));
|
|
472
|
+
|
|
473
|
+
const timeline = registry.getEventTimeline();
|
|
474
|
+
|
|
475
|
+
expect(timeline).toHaveLength(3);
|
|
476
|
+
expect(timeline[0].eventCount).toBe(1);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it('should support event export and import', () => {
|
|
480
|
+
const event1 = createEvent('test.event', { count: 1 });
|
|
481
|
+
const event2 = createEvent('test.event', { count: 2 });
|
|
482
|
+
|
|
483
|
+
registry.registerEvent(event1);
|
|
484
|
+
registry.registerEvent(event2);
|
|
485
|
+
|
|
486
|
+
const exportedEvents = registry.exportEvents();
|
|
487
|
+
registry.clearEvents();
|
|
488
|
+
|
|
489
|
+
expect(registry.getAllEvents()).toHaveLength(0);
|
|
490
|
+
|
|
491
|
+
registry.importEvents(exportedEvents);
|
|
492
|
+
|
|
493
|
+
expect(registry.getAllEvents()).toHaveLength(2);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it('should support getting events by category', () => {
|
|
497
|
+
const event1 = createEvent('test.event', { count: 1 }, { context: { category: 'database' } });
|
|
498
|
+
const event2 = createEvent('test.event', { count: 2 }, { context: { category: 'job-queue' } });
|
|
499
|
+
|
|
500
|
+
registry.registerEvent(event1);
|
|
501
|
+
registry.registerEvent(event2);
|
|
502
|
+
|
|
503
|
+
const databaseEvents = registry.getEventsByCategory('database');
|
|
504
|
+
const jobQueueEvents = registry.getEventsByCategory('job-queue');
|
|
505
|
+
|
|
506
|
+
expect(databaseEvents).toHaveLength(1);
|
|
507
|
+
expect(jobQueueEvents).toHaveLength(1);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('should support getting all events', () => {
|
|
511
|
+
const events = [
|
|
512
|
+
createEvent('test.event1', { count: 1 }),
|
|
513
|
+
createEvent('test.event2', { count: 2 }),
|
|
514
|
+
createEvent('test.event3', { count: 3 })
|
|
515
|
+
];
|
|
516
|
+
|
|
517
|
+
events.forEach(event => registry.registerEvent(event));
|
|
518
|
+
|
|
519
|
+
const allEvents = registry.getAllEvents();
|
|
520
|
+
|
|
521
|
+
expect(allEvents).toHaveLength(3);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it('should support clearing all events', () => {
|
|
525
|
+
const events = [
|
|
526
|
+
createEvent('test.event1', { count: 1 }),
|
|
527
|
+
createEvent('test.event2', { count: 2 })
|
|
528
|
+
];
|
|
529
|
+
|
|
530
|
+
events.forEach(event => registry.registerEvent(event));
|
|
531
|
+
|
|
532
|
+
registry.clearEvents();
|
|
533
|
+
|
|
534
|
+
expect(registry.getAllEvents()).toHaveLength(0);
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
describe('Event Categories', () => {
|
|
539
|
+
it('should create event categories correctly', () => {
|
|
540
|
+
const category = createEventCategory('database', 'Database operations', ['query', 'mutation']);
|
|
541
|
+
|
|
542
|
+
expect(category).toHaveProperty('name', 'database');
|
|
543
|
+
expect(category).toHaveProperty('description', 'Database operations');
|
|
544
|
+
expect(category).toHaveProperty('events', ['query', 'mutation']);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it('should support event category registration', () => {
|
|
548
|
+
const category = createEventCategory('database', 'Database operations');
|
|
549
|
+
manager.registerEventCategory(category);
|
|
550
|
+
|
|
551
|
+
expect(manager.getEventCategories()).toContainEqual(category);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it('should support multiple event categories', () => {
|
|
555
|
+
const databaseCategory = createEventCategory('database', 'Database operations', ['query', 'mutation']);
|
|
556
|
+
const jobQueueCategory = createEventCategory('job-queue', 'Job queue operations', ['job.created', 'job.started']);
|
|
557
|
+
|
|
558
|
+
manager.registerEventCategory(databaseCategory);
|
|
559
|
+
manager.registerEventCategory(jobQueueCategory);
|
|
560
|
+
|
|
561
|
+
const categories = manager.getEventCategories();
|
|
562
|
+
|
|
563
|
+
expect(categories).toHaveLength(2);
|
|
564
|
+
expect(categories).toContainEqual(databaseCategory);
|
|
565
|
+
expect(categories).toContainEqual(jobQueueCategory);
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
describe('Event Listeners', () => {
|
|
570
|
+
it('should create event listeners correctly', () => {
|
|
571
|
+
const handler = () => {};
|
|
572
|
+
const listener = createEventListener('test.listener', handler, {
|
|
573
|
+
priority: 10,
|
|
574
|
+
once: true
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
expect(listener).toHaveProperty('id');
|
|
578
|
+
expect(listener).toHaveProperty('name', 'test.listener');
|
|
579
|
+
expect(listener).toHaveProperty('handler', handler);
|
|
580
|
+
expect(listener).toHaveProperty('priority', 10);
|
|
581
|
+
expect(listener).toHaveProperty('once', true);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('should support listener priority', async () => {
|
|
585
|
+
const executionOrder = [];
|
|
586
|
+
const handlerHigh = jest.fn(() => executionOrder.push('high'));
|
|
587
|
+
const handlerLow = jest.fn(() => executionOrder.push('low'));
|
|
588
|
+
|
|
589
|
+
const listenerHigh = createEventListener('test.event', handlerHigh, { priority: 10 });
|
|
590
|
+
const listenerLow = createEventListener('test.event', handlerLow, { priority: 1 });
|
|
591
|
+
|
|
592
|
+
manager.addListener(listenerHigh);
|
|
593
|
+
manager.addListener(listenerLow);
|
|
594
|
+
|
|
595
|
+
await manager.emit(createEvent('test.event', {}));
|
|
596
|
+
|
|
597
|
+
expect(executionOrder).toEqual(['high', 'low']);
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it('should support listener removal', () => {
|
|
601
|
+
const handler = () => {};
|
|
602
|
+
const listener = createEventListener('test.listener', handler);
|
|
603
|
+
|
|
604
|
+
manager.addListener(listener);
|
|
605
|
+
manager.removeListener(listener);
|
|
606
|
+
|
|
607
|
+
expect(manager.getListeners('test.listener')).toHaveLength(0);
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
describe('Event Middleware', () => {
|
|
612
|
+
it('should create event middleware correctly', () => {
|
|
613
|
+
const middleware = createEventMiddleware(async (event, next) => {
|
|
614
|
+
await next();
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
expect(middleware).toBeInstanceOf(Function);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it('should support multiple middleware', async () => {
|
|
621
|
+
const eventData = { message: 'test' };
|
|
622
|
+
const event = createEvent('test.event', eventData);
|
|
623
|
+
|
|
624
|
+
const middleware1 = jest.fn(async (e, next) => {
|
|
625
|
+
e.data.middleware1 = true;
|
|
626
|
+
await next();
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
const middleware2 = jest.fn(async (e, next) => {
|
|
630
|
+
e.data.middleware2 = true;
|
|
631
|
+
await next();
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
manager.addMiddleware(middleware1);
|
|
635
|
+
manager.addMiddleware(middleware2);
|
|
636
|
+
|
|
637
|
+
const handler = jest.fn();
|
|
638
|
+
manager.on('test.event', handler);
|
|
639
|
+
|
|
640
|
+
await manager.emit(event);
|
|
641
|
+
|
|
642
|
+
expect(middleware1).toHaveBeenCalled();
|
|
643
|
+
expect(middleware2).toHaveBeenCalled();
|
|
644
|
+
expect(event.data.middleware1).toBe(true);
|
|
645
|
+
expect(event.data.middleware2).toBe(true);
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
describe('Event Filters', () => {
|
|
650
|
+
it('should create event filters correctly', () => {
|
|
651
|
+
const filter = createEventFilter((event) => event.data.type === 'important');
|
|
652
|
+
|
|
653
|
+
expect(filter).toBeInstanceOf(Function);
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
it('should support multiple filters', async () => {
|
|
657
|
+
const eventData1 = { type: 'important', value: 10 };
|
|
658
|
+
const eventData2 = { type: 'normal', value: 5 };
|
|
659
|
+
const eventData3 = { type: 'important', value: -1 };
|
|
660
|
+
const event1 = createEvent('test.event', eventData1);
|
|
661
|
+
const event2 = createEvent('test.event', eventData2);
|
|
662
|
+
const event3 = createEvent('test.event', eventData3);
|
|
663
|
+
|
|
664
|
+
const filter1 = (e: any) => e.data.type === 'important';
|
|
665
|
+
const filter2 = (e: any) => e.data.value > 0;
|
|
666
|
+
|
|
667
|
+
manager.addFilter(filter1);
|
|
668
|
+
manager.addFilter(filter2);
|
|
669
|
+
|
|
670
|
+
const handler = jest.fn();
|
|
671
|
+
manager.on('test.event', handler);
|
|
672
|
+
|
|
673
|
+
await manager.emit(event1);
|
|
674
|
+
await manager.emit(event2);
|
|
675
|
+
await manager.emit(event3);
|
|
676
|
+
|
|
677
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
678
|
+
expect(handler).toHaveBeenCalledWith(event1);
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
describe('Error Handling', () => {
|
|
683
|
+
it('should handle event validation errors', () => {
|
|
684
|
+
const invalidEvent = { name: 'test', data: {} };
|
|
685
|
+
|
|
686
|
+
expect(validateEvent(invalidEvent)).toBe(false);
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
it('should handle event serialization errors', () => {
|
|
690
|
+
const event = createEvent('test.event', { message: 'test' });
|
|
691
|
+
const serialized = serializeEvent(event);
|
|
692
|
+
|
|
693
|
+
expect(() => deserializeEvent('invalid-json')).toThrow();
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
it('should handle event processing errors', async () => {
|
|
697
|
+
const eventData = { message: 'test' };
|
|
698
|
+
const event = createEvent('test.event', eventData);
|
|
699
|
+
|
|
700
|
+
const failingHandler = jest.fn(() => {
|
|
701
|
+
throw new Error('Test error');
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
manager.on('test.event', failingHandler);
|
|
705
|
+
|
|
706
|
+
// Should not throw - errors are caught and handled gracefully
|
|
707
|
+
await manager.emit(event);
|
|
708
|
+
expect(failingHandler).toHaveBeenCalled();
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
it('should handle middleware errors', async () => {
|
|
712
|
+
const eventData = { message: 'test' };
|
|
713
|
+
const event = createEvent('test.event', eventData);
|
|
714
|
+
|
|
715
|
+
const failingMiddleware = jest.fn(async (e, next) => {
|
|
716
|
+
throw new Error('Middleware error');
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
manager.addMiddleware(failingMiddleware);
|
|
720
|
+
|
|
721
|
+
const handler = jest.fn();
|
|
722
|
+
manager.on('test.event', handler);
|
|
723
|
+
|
|
724
|
+
// Should not throw - errors are caught and handled gracefully
|
|
725
|
+
await manager.emit(event);
|
|
726
|
+
expect(failingMiddleware).toHaveBeenCalled();
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
it('should handle filter errors', async () => {
|
|
730
|
+
const eventData = { message: 'test' };
|
|
731
|
+
const event = createEvent('test.event', eventData);
|
|
732
|
+
|
|
733
|
+
const failingFilter = () => {
|
|
734
|
+
throw new Error('Filter error');
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
manager.addFilter(failingFilter);
|
|
738
|
+
|
|
739
|
+
const handler = jest.fn();
|
|
740
|
+
manager.on('test.event', handler);
|
|
741
|
+
|
|
742
|
+
// Should not throw - errors are caught and handled gracefully
|
|
743
|
+
await manager.emit(event);
|
|
744
|
+
expect(handler).not.toHaveBeenCalled(); // Filter failed, so handler shouldn't run
|
|
745
|
+
});
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
describe('Edge Cases and Error Conditions', () => {
|
|
749
|
+
it('should handle null or undefined events', async () => {
|
|
750
|
+
const handler = jest.fn();
|
|
751
|
+
manager.on('test.event', handler);
|
|
752
|
+
|
|
753
|
+
await expect(manager.emit(null as any)).rejects.toThrow();
|
|
754
|
+
await expect(manager.emit(undefined as any)).rejects.toThrow();
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
it('should handle null or undefined listeners', () => {
|
|
758
|
+
expect(() => manager.on('test.event', null as any)).toThrow();
|
|
759
|
+
expect(() => manager.on('test.event', undefined as any)).toThrow();
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
it('should handle duplicate listeners', () => {
|
|
763
|
+
const handler = () => {};
|
|
764
|
+
manager.on('test.event', handler);
|
|
765
|
+
manager.on('test.event', handler);
|
|
766
|
+
|
|
767
|
+
expect(manager.getListeners('test.event')).toHaveLength(2);
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
it('should handle empty event names', () => {
|
|
771
|
+
const eventData = { message: 'test' };
|
|
772
|
+
const event = createEvent('', eventData);
|
|
773
|
+
|
|
774
|
+
expect(() => validateEvent(event)).toThrow();
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
it('should handle empty event data', () => {
|
|
778
|
+
const event = createEvent('test.event', {});
|
|
779
|
+
|
|
780
|
+
expect(validateEvent(event)).toBe(true);
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
it('should handle maximum listeners limit', () => {
|
|
784
|
+
const managerWithLimit = createEventManager({ maxListeners: 2 });
|
|
785
|
+
|
|
786
|
+
const handler1 = () => {};
|
|
787
|
+
const handler2 = () => {};
|
|
788
|
+
const handler3 = () => {};
|
|
789
|
+
|
|
790
|
+
managerWithLimit.on('test.event', handler1);
|
|
791
|
+
managerWithLimit.on('test.event', handler2);
|
|
792
|
+
managerWithLimit.on('test.event', handler3);
|
|
793
|
+
|
|
794
|
+
expect(managerWithLimit.getListeners('test.event')).toHaveLength(3);
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
it('should handle maximum events limit in registry', () => {
|
|
798
|
+
const registryWithLimit = createEventRegistry({ maxEvents: 2 });
|
|
799
|
+
|
|
800
|
+
const event1 = createEvent('test.event1', { count: 1 });
|
|
801
|
+
const event2 = createEvent('test.event2', { count: 2 });
|
|
802
|
+
const event3 = createEvent('test.event3', { count: 3 });
|
|
803
|
+
|
|
804
|
+
registryWithLimit.registerEvent(event1);
|
|
805
|
+
registryWithLimit.registerEvent(event2);
|
|
806
|
+
registryWithLimit.registerEvent(event3);
|
|
807
|
+
|
|
808
|
+
expect(registryWithLimit.getAllEvents()).toHaveLength(3);
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
it('should handle invalid event categories', () => {
|
|
812
|
+
const invalidCategory = createEventCategory('', 'Invalid category');
|
|
813
|
+
|
|
814
|
+
expect(() => manager.registerEventCategory(invalidCategory)).not.toThrow();
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
it('should handle invalid event context', () => {
|
|
818
|
+
const event = createEvent('test.event', { message: 'test' }, { context: null as any });
|
|
819
|
+
|
|
820
|
+
expect(validateEvent(event)).toBe(true);
|
|
821
|
+
});
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
describe('Integration Tests', () => {
|
|
825
|
+
it('should handle complex event workflows', async () => {
|
|
826
|
+
const workflowEvents = [
|
|
827
|
+
createEvent('workflow.start', { step: 1 }),
|
|
828
|
+
createEvent('workflow.process', { step: 2 }),
|
|
829
|
+
createEvent('workflow.complete', { step: 3 })
|
|
830
|
+
];
|
|
831
|
+
|
|
832
|
+
const workflowResults = [];
|
|
833
|
+
|
|
834
|
+
const startHandler = jest.fn(async (event) => {
|
|
835
|
+
workflowResults.push(`Started: ${event.data.step}`);
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
const processHandler = jest.fn(async (event) => {
|
|
839
|
+
workflowResults.push(`Processing: ${event.data.step}`);
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
const completeHandler = jest.fn(async (event) => {
|
|
843
|
+
workflowResults.push(`Completed: ${event.data.step}`);
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
manager.on('workflow.start', startHandler);
|
|
847
|
+
manager.on('workflow.process', processHandler);
|
|
848
|
+
manager.on('workflow.complete', completeHandler);
|
|
849
|
+
|
|
850
|
+
for (const event of workflowEvents) {
|
|
851
|
+
await manager.emit(event);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
expect(workflowResults).toEqual([
|
|
855
|
+
'Started: 1',
|
|
856
|
+
'Processing: 2',
|
|
857
|
+
'Completed: 3'
|
|
858
|
+
]);
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
it('should handle event cascading', async () => {
|
|
862
|
+
const eventData = { message: 'test' };
|
|
863
|
+
const event = createEvent('trigger.event', eventData);
|
|
864
|
+
|
|
865
|
+
const triggerHandler = jest.fn(async (event) => {
|
|
866
|
+
await manager.emit(createEvent('cascaded.event', { source: 'trigger' }));
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
const cascadedHandler = jest.fn();
|
|
870
|
+
manager.on('trigger.event', triggerHandler);
|
|
871
|
+
manager.on('cascaded.event', cascadedHandler);
|
|
872
|
+
|
|
873
|
+
await manager.emit(event);
|
|
874
|
+
|
|
875
|
+
expect(triggerHandler).toHaveBeenCalled();
|
|
876
|
+
expect(cascadedHandler).toHaveBeenCalled();
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
it('should handle concurrent event emissions', async () => {
|
|
880
|
+
const eventData = { message: 'test' };
|
|
881
|
+
const event = createEvent('concurrent.event', eventData);
|
|
882
|
+
|
|
883
|
+
const handler = jest.fn(async () => {
|
|
884
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
manager.on('concurrent.event', handler);
|
|
888
|
+
|
|
889
|
+
const promises = Array(10).fill(null).map(() => manager.emit(event));
|
|
890
|
+
await Promise.all(promises);
|
|
891
|
+
|
|
892
|
+
expect(handler).toHaveBeenCalledTimes(10);
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
it('should handle event memory management', async () => {
|
|
896
|
+
const registryWithRetention = createEventRegistry({
|
|
897
|
+
retentionPeriod: 1000 // 1 second
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
const event = createEvent('test.event', { message: 'test' });
|
|
901
|
+
registryWithRetention.registerEvent(event);
|
|
902
|
+
|
|
903
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
904
|
+
|
|
905
|
+
registryWithRetention.cleanupOldEvents();
|
|
906
|
+
|
|
907
|
+
expect(registryWithRetention.getEvent(event.id)).toBeUndefined();
|
|
908
|
+
});
|
|
909
|
+
});
|
|
910
|
+
});
|