holosphere 1.1.9 → 1.1.10

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.
@@ -0,0 +1,329 @@
1
+ import HoloSphere from '../holosphere.js';
2
+ import { jest } from '@jest/globals';
3
+
4
+ // Increase the default test timeout
5
+ jest.setTimeout(30000);
6
+
7
+ describe('Subscription Tests', () => {
8
+ let holosphere;
9
+ const testHolon = `test_subscription_${Date.now()}`;
10
+ const testLens = 'items';
11
+
12
+ beforeEach(() => {
13
+ // Create a fresh HoloSphere instance for each test
14
+ holosphere = new HoloSphere('testApp', false);
15
+ });
16
+
17
+ afterEach(async () => {
18
+ // Clean up resources
19
+ try {
20
+ await holosphere.close();
21
+ } catch (error) {
22
+ console.warn('Error closing HoloSphere:', error);
23
+ }
24
+ jest.clearAllMocks();
25
+ });
26
+
27
+ test('should properly clean up subscription when unsubscribing', async () => {
28
+ // Create test data
29
+ const testData = {
30
+ id: 'test-item',
31
+ value: 'Test value'
32
+ };
33
+
34
+ // Create a mock callback function
35
+ function mockCallback (data) {
36
+ console.log('Callback received:', data);
37
+ }
38
+
39
+ // Set up subscription
40
+ const subscription = await holosphere.subscribe(testHolon, testLens, mockCallback);
41
+
42
+ // Verify subscription was created and stored
43
+ expect(Object.keys(holosphere.subscriptions).length).toBe(1);
44
+
45
+ // Get the subscription ID (should be the only key in the subscriptions object)
46
+ const subscriptionId = Object.keys(holosphere.subscriptions)[0];
47
+
48
+ // Verify the subscription object has the expected structure
49
+ expect(holosphere.subscriptions[subscriptionId]).toBeDefined();
50
+ expect(holosphere.subscriptions[subscriptionId].active).toBe(true);
51
+ expect(holosphere.subscriptions[subscriptionId].holon).toBe(testHolon);
52
+ expect(holosphere.subscriptions[subscriptionId].lens).toBe(testLens);
53
+ expect(holosphere.subscriptions[subscriptionId].callback).toBe(mockCallback);
54
+ expect(holosphere.subscriptions[subscriptionId].gunSubscription).toBeDefined();
55
+
56
+ // Now unsubscribe
57
+ await subscription.unsubscribe();
58
+
59
+ // Verify the subscription was properly cleaned up
60
+ expect(Object.keys(holosphere.subscriptions).length).toBe(0);
61
+ expect(holosphere.subscriptions[subscriptionId]).toBeUndefined();
62
+ });
63
+
64
+ test('should not receive updates after unsubscribing', async () => {
65
+ // Create test data
66
+ const testData = {
67
+ id: 'test-item',
68
+ value: 'Test value'
69
+ };
70
+
71
+ // Create a mock callback function
72
+ const mockCallback = jest.fn((data) => {
73
+ console.log('Callback received:', data);
74
+ });
75
+
76
+ // Set up subscription
77
+ const subscription = await holosphere.subscribe(testHolon, testLens, mockCallback);
78
+
79
+ // Store data to trigger subscription
80
+ await holosphere.put(testHolon, testLens, testData);
81
+
82
+ // Wait a bit for the subscription to trigger
83
+ await new Promise(resolve => setTimeout(resolve, 1000));
84
+
85
+ // Verify callback was called
86
+ expect(mockCallback).toHaveBeenCalled();
87
+
88
+ // Reset the mock
89
+ mockCallback.mockClear();
90
+
91
+ // Unsubscribe
92
+ await subscription.unsubscribe();
93
+
94
+ // Store more data
95
+ const newData = { ...testData, value: 'Updated value' };
96
+ await holosphere.put(testHolon, testLens, newData);
97
+
98
+ // Wait a bit to ensure no callbacks are triggered
99
+ await new Promise(resolve => setTimeout(resolve, 1000));
100
+
101
+ // Verify callback was NOT called after unsubscribing
102
+ expect(mockCallback).not.toHaveBeenCalled();
103
+ });
104
+
105
+ test('should handle multiple subscriptions and unsubscriptions correctly', async () => {
106
+ // Create mock callbacks
107
+ const mockCallback1 = jest.fn((data) => {
108
+ console.log('Callback 1 received:', data);
109
+ });
110
+ const mockCallback2 = jest.fn((data) => {
111
+ console.log('Callback 2 received:', data);
112
+ });
113
+ const mockCallback3 = jest.fn((data) => {
114
+ console.log('Callback 3 received:', data);
115
+ });
116
+
117
+ // Set up multiple subscriptions
118
+ const subscription1 = await holosphere.subscribe(testHolon, testLens, mockCallback1);
119
+ const subscription2 = await holosphere.subscribe(testHolon, testLens, mockCallback2);
120
+ const subscription3 = await holosphere.subscribe(testHolon, 'differentLens', mockCallback3);
121
+
122
+ // Verify subscriptions were created
123
+ expect(Object.keys(holosphere.subscriptions).length).toBe(3);
124
+
125
+ // Unsubscribe from one subscription
126
+ await subscription2.unsubscribe();
127
+
128
+ // Verify only the correct subscription was removed
129
+ expect(Object.keys(holosphere.subscriptions).length).toBe(2);
130
+
131
+ // Create test data
132
+ const testData = {
133
+ id: 'test-item',
134
+ value: 'Test value'
135
+ };
136
+
137
+ // Store data to trigger remaining subscriptions
138
+ await holosphere.put(testHolon, testLens, testData);
139
+
140
+ // Wait a bit for the subscription to trigger
141
+ await new Promise(resolve => setTimeout(resolve, 1000));
142
+
143
+ // Verify only active callbacks were called
144
+ expect(mockCallback1).toHaveBeenCalled();
145
+ expect(mockCallback2).not.toHaveBeenCalled();
146
+ expect(mockCallback3).not.toHaveBeenCalled(); // Different lens
147
+
148
+ // Unsubscribe from all remaining subscriptions
149
+ await subscription1.unsubscribe();
150
+ await subscription3.unsubscribe();
151
+
152
+ // Verify all subscriptions were removed
153
+ expect(Object.keys(holosphere.subscriptions).length).toBe(0);
154
+ });
155
+
156
+ test('should clean up subscriptions when closing HoloSphere instance', async () => {
157
+ // Create mock callback
158
+ const mockCallback = jest.fn((data) => {
159
+ console.log('Callback received:', data);
160
+ });
161
+
162
+ // Set up subscription
163
+ await holosphere.subscribe(testHolon, testLens, mockCallback);
164
+
165
+ // Verify subscription was created
166
+ expect(Object.keys(holosphere.subscriptions).length).toBe(1);
167
+
168
+ // Close the HoloSphere instance
169
+ await holosphere.close();
170
+
171
+ // Verify subscriptions were cleaned up
172
+ expect(Object.keys(holosphere.subscriptions).length).toBe(0);
173
+ });
174
+
175
+ test('should not leak memory after multiple subscription/unsubscription cycles', async () => {
176
+ // Record initial memory usage
177
+ const initialMemoryUsage = process.memoryUsage();
178
+
179
+ // Create 100 subscriptions and immediately unsubscribe from them
180
+ for (let i = 0; i < 100; i++) {
181
+ // Create a proper callback function instead of a jest mock
182
+ const mockCallback = (data) => {
183
+ // Simple callback implementation
184
+ console.log('Data received:', data);
185
+ };
186
+
187
+ const subscription = await holosphere.subscribe(testHolon, `${testLens}_${i}`, mockCallback);
188
+
189
+ // Verify subscription was created
190
+ expect(Object.keys(holosphere.subscriptions).length).toBe(1);
191
+
192
+ // Unsubscribe
193
+ await subscription.unsubscribe();
194
+
195
+ // Verify subscription was removed
196
+ expect(Object.keys(holosphere.subscriptions).length).toBe(0);
197
+ }
198
+
199
+ // Force garbage collection if possible (Node.js with --expose-gc flag)
200
+ if (global.gc) {
201
+ global.gc();
202
+ }
203
+
204
+ // Record final memory usage
205
+ const finalMemoryUsage = process.memoryUsage();
206
+
207
+ // Check if heap usage has significantly increased (more than 50MB would be suspicious)
208
+ const heapIncrease = finalMemoryUsage.heapUsed - initialMemoryUsage.heapUsed;
209
+ console.log(`Memory increase after subscriptions: ${heapIncrease / 1024 / 1024} MB`);
210
+
211
+ // The exact threshold depends on your environment, but we expect it to be relatively small
212
+ // This is a loose check, as garbage collection is unpredictable
213
+ expect(heapIncrease).toBeLessThan(50 * 1024 * 1024); // Less than 50MB increase
214
+
215
+ // Verify all subscriptions are cleaned up
216
+ expect(Object.keys(holosphere.subscriptions).length).toBe(0);
217
+ });
218
+
219
+ test('should properly handle subscription to data with references', async () => {
220
+ // Use simple holon names that don't require H3 formats
221
+ const originHolon = `origin_${Date.now()}`;
222
+ const referenceHolon = `reference_${Date.now()}`;
223
+
224
+ // Create actual data to store in the origin holon
225
+ const originalData = {
226
+ id: `test-data-${Date.now()}`,
227
+ title: 'Original Data',
228
+ content: 'This is the original data content',
229
+ timestamp: Date.now()
230
+ };
231
+
232
+ // Store the data in the origin holon
233
+ await holosphere.put(originHolon, testLens, originalData);
234
+
235
+ // Create a reference manually
236
+ const soulPath = `${holosphere.appname}/${originHolon}/${testLens}/${originalData.id}`;
237
+ const referenceData = {
238
+ id: originalData.id,
239
+ soul: soulPath
240
+ };
241
+
242
+ // Store the reference in the reference holon
243
+ await holosphere.put(referenceHolon, testLens, referenceData);
244
+
245
+ // Create a data collection to store received data
246
+ const receivedData = [];
247
+
248
+ // Create a real callback that stores the data
249
+ const dataCallback = (data) => {
250
+ if (data) {
251
+ receivedData.push(data);
252
+ }
253
+ };
254
+
255
+ // Subscribe to the reference holon
256
+ const subscription = await holosphere.subscribe(referenceHolon, testLens, dataCallback);
257
+
258
+ // Wait for subscription to receive data
259
+ await new Promise(resolve => setTimeout(resolve, 1000));
260
+
261
+ // Update the original data
262
+ const updatedData = {
263
+ ...originalData,
264
+ title: 'Updated Data',
265
+ content: 'This content has been updated',
266
+ timestamp: Date.now()
267
+ };
268
+
269
+ // Update the data in the original location
270
+ await holosphere.put(originHolon, testLens, updatedData);
271
+
272
+ // Wait for update to propagate through reference
273
+ await new Promise(resolve => setTimeout(resolve, 2000));
274
+
275
+ // Unsubscribe to clean up
276
+ await subscription.unsubscribe();
277
+
278
+ // Verify the subscription was properly cleaned up
279
+ expect(Object.keys(holosphere.subscriptions).length).toBe(0);
280
+
281
+ // Verify that data was received
282
+ console.log('Received data:', receivedData);
283
+ expect(receivedData.length).toBeGreaterThan(0);
284
+
285
+ // Get the data directly from the reference holon
286
+ // This should be the reference object with a soul property
287
+ const directData = await holosphere.get(referenceHolon, testLens, originalData.id);
288
+ console.log('Direct data from reference holon:', directData);
289
+
290
+ // Check if we got the reference back
291
+ if (directData) {
292
+ // The reference might be auto-resolved, in which case it will have a _federation property
293
+ // instead of a direct soul property
294
+ if (directData._federation) {
295
+ expect(directData._federation).toBeTruthy();
296
+ expect(directData._federation.soul).toEqual(soulPath);
297
+ expect(directData._federation.resolved).toBe(true);
298
+ } else if (directData.soul) {
299
+ // Or it might be a direct reference
300
+ expect(directData.soul).toBeTruthy();
301
+ expect(directData.soul).toEqual(soulPath);
302
+ }
303
+ }
304
+
305
+ // Try to resolve the reference to verify it works correctly
306
+ const resolvedData = await holosphere.get(referenceHolon, testLens, originalData.id, null, { resolveReferences: true });
307
+ console.log('Resolved data:', resolvedData);
308
+
309
+ // The resolved data should not be null
310
+ expect(resolvedData).toBeTruthy();
311
+
312
+ // If the data was properly resolved, it should match the updated data
313
+ if (resolvedData && resolvedData.title) {
314
+ expect(resolvedData.title).toBe(updatedData.title);
315
+ expect(resolvedData.content).toBe(updatedData.content);
316
+ }
317
+
318
+ // Now try to get the original data directly
319
+ const originalDataRetrieved = await holosphere.get(originHolon, testLens, originalData.id);
320
+ console.log('Original data retrieved:', originalDataRetrieved);
321
+
322
+ // Verify it was updated
323
+ expect(originalDataRetrieved).toBeTruthy();
324
+ if (originalDataRetrieved) {
325
+ expect(originalDataRetrieved.title).toBe(updatedData.title);
326
+ expect(originalDataRetrieved.content).toBe(updatedData.content);
327
+ }
328
+ });
329
+ });