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.
- package/federation.js +67 -21
- package/holosphere.js +578 -438
- package/package.json +1 -1
- package/test/auth.test.js +35 -19
- package/test/delete.test.js +1 -0
- package/test/holosphere.test.js +189 -5
- package/test/reference.test.js +211 -0
- package/test/subscription.test.js +329 -0
|
@@ -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
|
+
});
|