@opendevstack/ngx-appshell 19.0.5

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.
Files changed (71) hide show
  1. package/README.md +131 -0
  2. package/fesm2022/opendevstack-ngx-appshell.mjs +727 -0
  3. package/fesm2022/opendevstack-ngx-appshell.mjs.map +1 -0
  4. package/index.d.ts +5 -0
  5. package/lib/components/appshell-breadcrumb/appshell-breadcrumb.component.d.ts +7 -0
  6. package/lib/components/appshell-chip/appshell-chip.component.d.ts +6 -0
  7. package/lib/components/appshell-filters/appshell-filters.component.d.ts +12 -0
  8. package/lib/components/appshell-header/appshell-header.component.d.ts +24 -0
  9. package/lib/components/appshell-icon/appshell-icon.component.d.ts +12 -0
  10. package/lib/components/appshell-layout/appshell-layout.component.d.ts +25 -0
  11. package/lib/components/appshell-page-header/appshell-page-header.component.d.ts +18 -0
  12. package/lib/components/appshell-platform-header/appshell-platform-header.component.d.ts +37 -0
  13. package/lib/components/appshell-platform-layout/appshell-platform-layout.component.d.ts +30 -0
  14. package/lib/components/appshell-product-card/appshell-product-card.component.d.ts +23 -0
  15. package/lib/components/appshell-product-card-v2/appshell-product-card-v2.component.d.ts +16 -0
  16. package/lib/components/appshell-select/appshell-select.component.d.ts +9 -0
  17. package/lib/components/appshell-sidebar-menu/appshell-sidebar-menu.component.d.ts +12 -0
  18. package/lib/components/appshell-toast/appshell-toast.component.d.ts +9 -0
  19. package/lib/components/appshell-toasts/appshell-toasts.component.d.ts +14 -0
  20. package/lib/components/index.d.ts +15 -0
  21. package/lib/directives/appshell-link.directive.d.ts +15 -0
  22. package/lib/directives/index.d.ts +1 -0
  23. package/lib/models/appshell-button.d.ts +5 -0
  24. package/lib/models/appshell-filter.d.ts +4 -0
  25. package/lib/models/appshell-link.d.ts +6 -0
  26. package/lib/models/appshell-links-group.d.ts +5 -0
  27. package/lib/models/appshell-notification.d.ts +10 -0
  28. package/lib/models/appshell-picker.d.ts +9 -0
  29. package/lib/models/appshell-product.d.ts +12 -0
  30. package/lib/models/appshell-tag.d.ts +4 -0
  31. package/lib/models/appshell-toast.d.ts +6 -0
  32. package/lib/models/appshell-user.d.ts +5 -0
  33. package/lib/models/index.d.ts +10 -0
  34. package/lib/screens/appshell-notifications-screen/appshell-notifications-screen.component.d.ts +23 -0
  35. package/lib/screens/appshell-product-catalog-screen/appshell-product-catalog-screen.component.d.ts +16 -0
  36. package/lib/screens/appshell-product-view-screen/appshell-product-view-screen.component.d.ts +19 -0
  37. package/lib/screens/index.d.ts +3 -0
  38. package/lib/services/appshell-toast.service.d.ts +12 -0
  39. package/lib/services/icon-registry.service.d.ts +14 -0
  40. package/lib/services/index.d.ts +2 -0
  41. package/opendevstack-ngx-appshell-19.0.5.tgz +0 -0
  42. package/package.json +44 -0
  43. package/public-api.d.ts +5 -0
  44. package/schematics/azure-login/files/app-config/config.json.template +12 -0
  45. package/schematics/azure-login/files/app-config-service/app-config.service.spec.ts.template +48 -0
  46. package/schematics/azure-login/files/app-config-service/app-config.service.ts.template +39 -0
  47. package/schematics/azure-login/files/azure-config/azure.config.ts.template +94 -0
  48. package/schematics/azure-login/files/azure-service/azure.service.spec.ts.template +311 -0
  49. package/schematics/azure-login/files/azure-service/azure.service.ts.template +161 -0
  50. package/schematics/azure-login/index.d.ts +2 -0
  51. package/schematics/azure-login/index.js +325 -0
  52. package/schematics/azure-login/index.js.map +1 -0
  53. package/schematics/azure-login/schema.json +8 -0
  54. package/schematics/collection.json +19 -0
  55. package/schematics/nats-notifications/files/nats-service/nats.service.spec.ts.template +473 -0
  56. package/schematics/nats-notifications/files/nats-service/nats.service.ts.template +255 -0
  57. package/schematics/nats-notifications/files/notifications-screen/notifications-screen.component.html.template +7 -0
  58. package/schematics/nats-notifications/files/notifications-screen/notifications-screen.component.spec.ts.template +152 -0
  59. package/schematics/nats-notifications/files/notifications-screen/notifications-screen.component.ts.template +61 -0
  60. package/schematics/nats-notifications/index.d.ts +2 -0
  61. package/schematics/nats-notifications/index.js +502 -0
  62. package/schematics/nats-notifications/index.js.map +1 -0
  63. package/schematics/nats-notifications/schema.json +8 -0
  64. package/schematics/ng-add/files/_fonts.scss +85 -0
  65. package/schematics/ng-add/files/styles.scss +47 -0
  66. package/schematics/ng-add/index.d.ts +2 -0
  67. package/schematics/ng-add/index.js +442 -0
  68. package/schematics/ng-add/index.js.map +1 -0
  69. package/styles/appshell-typography-config.scss +19 -0
  70. package/styles/appshell.theme.scss +75 -0
  71. package/styles/palette.css +92 -0
@@ -0,0 +1,473 @@
1
+ import { TestBed, fakeAsync, tick } from '@angular/core/testing';
2
+ import { NatsService, NatsMessage } from './nats.service';
3
+ import * as natsCore from '@nats-io/nats-core';
4
+ import * as jetstreamModule from '@nats-io/jetstream';
5
+ import * as kvModule from '@nats-io/kv';
6
+
7
+ describe('NatsService', () => {
8
+ let service: NatsService;
9
+ let mockConnection: jasmine.SpyObj<natsCore.NatsConnection>;
10
+ let mockKvm: jasmine.SpyObj<kvModule.Kvm>;
11
+ let mockKv: jasmine.SpyObj<kvModule.KV>;
12
+ let mockJsm: any;
13
+ let mockStreams: any;
14
+ let mockConsumers: any;
15
+ let mockJetstream: any;
16
+ let mockConsumer: any;
17
+ let mockIterator: any;
18
+ let mockSubscription: jasmine.SpyObj<natsCore.Subscription>;
19
+
20
+ let wsconnectSpy: jasmine.Spy;
21
+ let jetstreamManagerSpy: jasmine.Spy;
22
+ let kvmSpy: jasmine.Spy;
23
+
24
+ let originalWsconnectDescriptor: PropertyDescriptor | undefined;
25
+ let originalJetstreamManagerDescriptor: PropertyDescriptor | undefined;
26
+ let originalKvmDescriptor: PropertyDescriptor | undefined;
27
+
28
+ beforeAll(() => {
29
+ originalWsconnectDescriptor = Object.getOwnPropertyDescriptor(natsCore, 'wsconnect');
30
+ originalJetstreamManagerDescriptor = Object.getOwnPropertyDescriptor(jetstreamModule, 'jetstreamManager');
31
+ originalKvmDescriptor = Object.getOwnPropertyDescriptor(kvModule, 'Kvm');
32
+ });
33
+
34
+ afterAll(() => {
35
+ if (originalWsconnectDescriptor) {
36
+ Object.defineProperty(natsCore, 'wsconnect', originalWsconnectDescriptor);
37
+ }
38
+ if (originalJetstreamManagerDescriptor) {
39
+ Object.defineProperty(jetstreamModule, 'jetstreamManager', originalJetstreamManagerDescriptor);
40
+ }
41
+ if (originalKvmDescriptor) {
42
+ Object.defineProperty(kvModule, 'Kvm', originalKvmDescriptor);
43
+ }
44
+ });
45
+
46
+ beforeEach(() => {
47
+ mockConnection = jasmine.createSpyObj('NatsConnection', ['close']);
48
+ mockKvm = jasmine.createSpyObj('Kvm', ['create']);
49
+ mockKv = jasmine.createSpyObj('KV', ['get', 'put']);
50
+ mockSubscription = jasmine.createSpyObj('Subscription', ['unsubscribe']);
51
+
52
+ mockConsumer = {
53
+ consume: jasmine.createSpy('consume').and.returnValue(Promise.resolve({
54
+ [Symbol.asyncIterator]: () => ({
55
+ next: jasmine.createSpy('next').and.resolveTo({
56
+ done: true
57
+ })
58
+ })
59
+ }))
60
+ };
61
+
62
+ mockConsumers = {
63
+ add: jasmine.createSpy('add').and.returnValue(Promise.resolve({ name: 'test-consumer' })),
64
+ get: jasmine.createSpy('get').and.returnValue(Promise.resolve(mockConsumer))
65
+ };
66
+
67
+ mockStreams = {
68
+ find: jasmine.createSpy('find').and.returnValue(Promise.resolve('test-stream'))
69
+ };
70
+
71
+ mockJetstream = jasmine.createSpy('jetstream').and.returnValue({ consumers: mockConsumers });
72
+
73
+ mockJsm = {
74
+ streams: mockStreams,
75
+ consumers: mockConsumers,
76
+ jetstream: mockJetstream
77
+ };
78
+
79
+ wsconnectSpy = jasmine.createSpy('wsconnect').and.returnValue(Promise.resolve(mockConnection));
80
+ jetstreamManagerSpy = jasmine.createSpy('jetstreamManager').and.returnValue(Promise.resolve(mockJsm));
81
+ kvmSpy = jasmine.createSpy('Kvm').and.returnValue(mockKvm);
82
+
83
+ Object.defineProperty(natsCore, 'wsconnect', { get: () => wsconnectSpy });
84
+ Object.defineProperty(jetstreamModule, 'jetstreamManager', { get: () => jetstreamManagerSpy });
85
+ Object.defineProperty(kvModule, 'Kvm', { get: () => kvmSpy });
86
+
87
+ mockIterator = {
88
+ [Symbol.asyncIterator]: function() {
89
+ let count = 0;
90
+ const messages = [
91
+ {
92
+ subject: 'app.com.notifications.public',
93
+ json: jasmine.createSpy('json').and.returnValue({ type: 'alert', title: 'Test Alert', date: new Date().toISOString() }),
94
+ headers: { get: (key: string) => key === 'Nats-Msg-Id' ? 'msg-1' : '' },
95
+ info: { pending: 1 }
96
+ },
97
+ {
98
+ subject: 'app.com.notifications.public',
99
+ json: jasmine.createSpy('json').and.returnValue({ type: 'info', title: 'Test Info', date: new Date().toISOString() }),
100
+ headers: { get: (key: string) => null },
101
+ info: { pending: 0 }
102
+ }
103
+ ];
104
+
105
+ return {
106
+ next: async () => {
107
+ if (count < messages.length) {
108
+ return { value: messages[count++], done: false };
109
+ } else {
110
+ return { done: true };
111
+ }
112
+ }
113
+ };
114
+ }
115
+ };
116
+
117
+ mockConsumer.consume.and.returnValue(Promise.resolve(mockIterator));
118
+
119
+ mockKvm.create.and.returnValue(Promise.resolve(mockKv));
120
+
121
+ TestBed.configureTestingModule({
122
+ providers: [NatsService]
123
+ });
124
+
125
+ service = TestBed.inject(NatsService);
126
+ });
127
+
128
+ it('should be created', () => {
129
+ expect(service).toBeTruthy();
130
+ });
131
+
132
+ describe('initialize', () => {
133
+ it('should establish a NATS connection', async () => {
134
+ await service.initialize('ws://localhost:4222');
135
+ expect(wsconnectSpy).toHaveBeenCalledWith({ servers: 'ws://localhost:4222' });
136
+ });
137
+
138
+ it('should handle connection errors', async () => {
139
+ const testError = new Error('Connection failed');
140
+ wsconnectSpy.and.returnValue(Promise.reject(testError));
141
+
142
+ let capturedError: Error | undefined;
143
+ service.connectionError$.subscribe(error => {
144
+ capturedError = error;
145
+ });
146
+
147
+ await expectAsync(service.initialize('ws://localhost:4222')).toBeRejectedWith(testError);
148
+ expect(capturedError).toBe(testError);
149
+ });
150
+ });
151
+
152
+ describe('initializeUser', () => {
153
+ beforeEach(async () => {
154
+ await service.initialize('ws://localhost:4222');
155
+ });
156
+
157
+ it('should initialize KV store and load messages', async () => {
158
+ const mockEntry = {
159
+ string: jasmine.createSpy('string').and.returnValue('[]')
160
+ };
161
+ mockKv.get.and.returnValue(Promise.resolve(mockEntry as unknown as kvModule.KvEntry));
162
+
163
+ await service.initializeUser('user123');
164
+
165
+ expect(kvmSpy).toHaveBeenCalledWith(mockConnection);
166
+ expect(mockKvm.create).toHaveBeenCalledWith('bucket-user123');
167
+
168
+ expect(mockKv.get).toHaveBeenCalledWith('app.com.notifications.public.user123.read');
169
+ expect(mockKv.get).toHaveBeenCalledWith('app.com.notifications.private.user123.read');
170
+
171
+ expect(jetstreamManagerSpy).toHaveBeenCalledWith(mockConnection);
172
+ });
173
+
174
+ it('should throw an error if connection is not established when initializing kv store', async () => {
175
+ (service as any).connection = null;
176
+
177
+ await expectAsync((service as any).initializeKvStore('user123')).toBeRejectedWithError('NATS connection not established properly');
178
+ });
179
+
180
+ it('should handle errors in KV store initialization', async () => {
181
+ const testError = new Error('KV creation failed');
182
+ mockKvm.create.and.rejectWith(testError);
183
+
184
+ let capturedError: Error | undefined;
185
+ service.connectionError$.subscribe(error => {
186
+ capturedError = error;
187
+ });
188
+
189
+ await expectAsync(service.initializeUser('user123')).toBeRejectedWith(testError);
190
+ expect(capturedError).toBe(testError);
191
+ });
192
+
193
+ it('should throw an error if connection is not established when loading messages', async () => {
194
+ (service as any).connection = null;
195
+
196
+ await expectAsync((service as any).loadMessages('user123')).toBeRejectedWithError('NATS connection or KV client not initialized');
197
+ });
198
+
199
+ it('should handle errors while processing messages', async () => {
200
+ spyOn(service, 'getReadMessageIds').and.returnValue(Promise.resolve([]));
201
+
202
+ const invalidMessage = {
203
+ subject: 'app.com.notifications.public',
204
+ json: jasmine.createSpy('json').and.throwError('Invalid message format'),
205
+ headers: { get: jasmine.createSpy('get').and.returnValue('msg-invalid') },
206
+ info: { pending: 0 }
207
+ };
208
+
209
+ mockIterator[Symbol.asyncIterator] = function () {
210
+ let count = 0;
211
+ return {
212
+ next: async () => {
213
+ if (count === 0) {
214
+ count++;
215
+ return { value: invalidMessage, done: false };
216
+ }
217
+ return { done: true };
218
+ }
219
+ };
220
+ };
221
+
222
+ const consoleErrorSpy = spyOn(console, 'error');
223
+
224
+ await service.initialize('ws://localhost:4222');
225
+ await service.initializeUser('user123');
226
+
227
+ await new Promise(resolve => setTimeout(resolve, 0));
228
+
229
+ expect(consoleErrorSpy).toHaveBeenCalledWith('Error processing message:', jasmine.any(Error));
230
+ });
231
+
232
+ it('should handle errors when loading initial messages from stream', async () => {
233
+ const mockEntry = {
234
+ string: jasmine.createSpy('string').and.returnValue('[]')
235
+ };
236
+ mockKv.get.and.returnValue(Promise.resolve(mockEntry as unknown as kvModule.KvEntry));
237
+ await service.initializeUser('user123');
238
+ mockStreams.find.and.throwError(new Error('Stream loading failed'));
239
+
240
+ await expectAsync((service as any).loadMessages('user123')).toBeRejectedWithError('Stream loading failed');
241
+ });
242
+ });
243
+
244
+ describe('readMessages', () => {
245
+ beforeEach(async () => {
246
+ await service.initialize('ws://localhost:4222');
247
+
248
+ const mockEntry = {
249
+ string: jasmine.createSpy('string').and.returnValue('["existing-id"]')
250
+ };
251
+ mockKv.get.and.returnValue(Promise.resolve(mockEntry as unknown as kvModule.KvEntry));
252
+
253
+ await service.initializeUser('user123');
254
+ });
255
+
256
+ it('should mark messages as read', async () => {
257
+ mockKv.get.calls.reset();
258
+ mockKv.get.and.returnValue(Promise.resolve({
259
+ string: jasmine.createSpy('string').and.returnValue('["existing-id"]')
260
+ } as unknown as kvModule.KvEntry));
261
+
262
+ await service.readMessages('subject1', ['msg-1', 'msg-2']);
263
+
264
+ expect(mockKv.get).toHaveBeenCalledWith('subject1');
265
+ expect(mockKv.put).toHaveBeenCalledWith('subject1', '["existing-id","msg-1","msg-2"]');
266
+ });
267
+
268
+ it('should not add duplicate message IDs', async () => {
269
+ mockKv.get.calls.reset();
270
+ mockKv.get.and.returnValue(Promise.resolve({
271
+ string: jasmine.createSpy('string').and.returnValue('["msg-1","existing-id"]')
272
+ } as unknown as kvModule.KvEntry));
273
+
274
+ await service.readMessages('subject1', ['msg-1', 'msg-2']);
275
+
276
+ expect(mockKv.put).toHaveBeenCalledWith('subject1', '["msg-1","existing-id","msg-2"]');
277
+ });
278
+
279
+ it('should throw an error if KV store is not initialized', async () => {
280
+ (service as any).kv = null;
281
+
282
+ await expectAsync(service.readMessages('subject1', ['msg-1'])).toBeRejectedWithError('KV store not initialized');
283
+ });
284
+
285
+ it('should throw an error if cannot mark as read', async () => {
286
+ mockKv.get.and.throwError(new Error('fake'))
287
+ await expectAsync(service.readMessages('subject1', ['msg-1'])).toBeRejectedWithError('fake');
288
+ });
289
+ });
290
+
291
+ describe('getReadMessageIds', () => {
292
+ beforeEach(async () => {
293
+ await service.initialize('ws://localhost:4222');
294
+ await service.initializeUser('user123');
295
+ });
296
+
297
+ it('should return read message IDs from KV store', async () => {
298
+ mockKv.get.and.returnValue(Promise.resolve({
299
+ string: jasmine.createSpy('string').and.returnValue('["msg-1","msg-2"]')
300
+ } as unknown as kvModule.KvEntry));
301
+
302
+ const readIds = await service.getReadMessageIds('subject1');
303
+
304
+ expect(readIds).toEqual(['msg-1', 'msg-2']);
305
+ expect(mockKv.get).toHaveBeenCalledWith('subject1');
306
+ });
307
+
308
+ it('should return empty array if key not found', async () => {
309
+ mockKv.get.and.returnValue(Promise.resolve(null));
310
+
311
+ const readIds = await service.getReadMessageIds('subject1');
312
+
313
+ expect(readIds).toEqual([]);
314
+ });
315
+
316
+ it('should throw error if KV store not initialized', async () => {
317
+ (service as any).kv = null;
318
+
319
+ await expectAsync(service.getReadMessageIds('subject1')).toBeRejectedWithError('KV store not initialized');
320
+ });
321
+
322
+
323
+ it('should throw an error if cannot read', async () => {
324
+ mockKv.get.and.throwError(new Error('fake'))
325
+ await expectAsync(service.getReadMessageIds('subject1')).toBeRejectedWithError('fake');
326
+ });
327
+ });
328
+
329
+ describe('isMessageRead', () => {
330
+ beforeEach(async () => {
331
+ await service.initialize('ws://localhost:4222');
332
+ await service.initializeUser('user123');
333
+
334
+ spyOn(service, 'getReadMessageIds').and.returnValue(Promise.resolve(['msg-1', 'msg-3']));
335
+ });
336
+
337
+ it('should return true for read messages', async () => {
338
+ const isRead = await service.isMessageRead('subject1', 'msg-1');
339
+ expect(isRead).toBeTrue();
340
+ expect(service.getReadMessageIds).toHaveBeenCalledWith('subject1');
341
+ });
342
+
343
+ it('should return false for unread messages', async () => {
344
+ const isRead = await service.isMessageRead('subject1', 'msg-2');
345
+ expect(isRead).toBeFalse();
346
+ });
347
+ });
348
+
349
+ describe('close', () => {
350
+ it('should unsubscribe from all subscriptions and close connection', async () => {
351
+ await service.initialize('ws://localhost:4222');
352
+ (service as any).subscriptions.set('subject1', mockSubscription);
353
+ (service as any).subscriptions.set('subject2', mockSubscription);
354
+
355
+ await service.close();
356
+
357
+ expect(mockSubscription.unsubscribe).toHaveBeenCalledTimes(2);
358
+ expect((service as any).subscriptions.size).toBe(0);
359
+
360
+ expect(mockConnection.close).toHaveBeenCalledTimes(1);
361
+ expect((service as any).connection).toBeNull();
362
+ });
363
+
364
+ it('should catch the errors if there are failures', async () => {
365
+ await service.initialize('ws://localhost:4222');
366
+ (service as any).subscriptions.set('subject1', mockSubscription);
367
+ mockSubscription.unsubscribe.and.throwError(new Error('fake'));
368
+ await expectAsync(service.close()).toBeResolved();
369
+ });
370
+ });
371
+
372
+ describe('isValidMessage', () => {
373
+ it('should return true for valid messages', () => {
374
+ const validMessage = {
375
+ type: 'alert',
376
+ title: 'Test Alert',
377
+ date: new Date().toISOString()
378
+ };
379
+
380
+ expect(service.isValidMessage(validMessage)).toBeTrue();
381
+ });
382
+
383
+ it('should return false for messages missing required fields', () => {
384
+ const invalidMessage1 = {
385
+ title: 'Test Alert',
386
+ date: new Date().toISOString()
387
+ };
388
+
389
+ const invalidMessage2 = {
390
+ type: 'alert',
391
+ date: new Date().toISOString()
392
+ };
393
+
394
+ expect(service.isValidMessage(invalidMessage1)).toBeFalse();
395
+ expect(service.isValidMessage(invalidMessage2)).toBeFalse();
396
+ });
397
+
398
+ it('should return false for messages with invalid date', () => {
399
+ const invalidMessage = {
400
+ type: 'alert',
401
+ title: 'Test Alert',
402
+ date: 'not-a-date'
403
+ };
404
+
405
+ expect(service.isValidMessage(invalidMessage)).toBeFalse();
406
+ });
407
+ });
408
+
409
+ describe('ngOnDestroy', () => {
410
+ it('should call close method', () => {
411
+ spyOn(service, 'close').and.returnValue(Promise.resolve());
412
+
413
+ service.ngOnDestroy();
414
+
415
+ expect(service.close).toHaveBeenCalledTimes(1);
416
+ });
417
+
418
+ it('should handle errors when closing', fakeAsync(() => {
419
+ const consoleErrorSpy = spyOn(console, 'error');
420
+ const error = new Error('Close error');
421
+ spyOn(service, 'close').and.returnValue(Promise.reject(error));
422
+
423
+ service.ngOnDestroy();
424
+ tick();
425
+
426
+ expect(consoleErrorSpy).toHaveBeenCalledWith('Error closing NATS connection', error);
427
+ }));
428
+ });
429
+
430
+ describe('loadMessages', () => {
431
+ beforeEach(async () => {
432
+ await service.initialize('ws://localhost:4222');
433
+
434
+ const mockEntry = {
435
+ string: jasmine.createSpy('string').and.returnValue('[]')
436
+ };
437
+ mockKv.get.and.returnValue(Promise.resolve(mockEntry as unknown as kvModule.KvEntry));
438
+ });
439
+
440
+ it('should load messages from streams and update subjects', async () => {
441
+ spyOn(service, 'getReadMessageIds').and.returnValue(Promise.resolve([]));
442
+
443
+ let capturedMessages: NatsMessage[] = [];
444
+ service.messages$.subscribe(messages => {
445
+ capturedMessages = messages;
446
+ });
447
+
448
+ let capturedLiveMessage: NatsMessage | null = null;
449
+ service.liveMessage$.subscribe(message => {
450
+ capturedLiveMessage = message;
451
+ });
452
+
453
+ await service.initializeUser('user123');
454
+
455
+ await new Promise(resolve => setTimeout(resolve, 0));
456
+
457
+ expect(mockStreams.find).toHaveBeenCalledWith('app.com.notifications.public');
458
+ expect(mockStreams.find).toHaveBeenCalledWith('app.com.notifications.private.user123');
459
+ expect(mockConsumers.add).toHaveBeenCalledTimes(2);
460
+ expect(mockConsumer.consume).toHaveBeenCalledTimes(2);
461
+ });
462
+
463
+ it('should handle stream not found', async () => {
464
+ mockStreams.find.and.returnValue(Promise.resolve(null));
465
+
466
+ const consoleErrorSpy = spyOn(console, 'error');
467
+
468
+ await service.initializeUser('user123');
469
+
470
+ expect(consoleErrorSpy).toHaveBeenCalledWith('Stream not found for subject: app.com.notifications.public');
471
+ });
472
+ });
473
+ });