holosphere 1.1.2 → 1.1.4

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.
@@ -2,15 +2,43 @@ import HoloSphere from '../holosphere.js';
2
2
  import * as h3 from 'h3-js';
3
3
 
4
4
  describe('HoloSphere', () => {
5
- let holoSphere;
6
5
  const testAppName = 'test-app';
7
-
8
- beforeEach(() => {
9
- holoSphere = new HoloSphere(testAppName);
6
+ const testCredentials = {
7
+ spacename: 'testuser',
8
+ password: 'testpass'
9
+ };
10
+ let holoSphere = new HoloSphere(testAppName, false);
11
+ beforeAll(async () => {
12
+ // Initialize HoloSphere once for all tests
13
+
14
+
15
+ // Set up test space and authenticate
16
+ try {
17
+ await holoSphere.createSpace(testCredentials.spacename, testCredentials.password);
18
+ } catch (error) {
19
+ // Space might already exist, try to delete it first
20
+ try {
21
+ await holoSphere.deleteGlobal('spaces', testCredentials.spacename);
22
+ await holoSphere.createSpace(testCredentials.spacename, testCredentials.password);
23
+ } catch (error) {
24
+ console.error('Failed to recreate space:', error);
25
+ throw error;
26
+ }
27
+ }
28
+
29
+ // Ensure we're logged in
30
+ await holoSphere.login(testCredentials.spacename, testCredentials.password);
31
+ });
32
+
33
+ beforeEach(async () => {
34
+ // Ensure we're logged in before each test
35
+ if (!holoSphere.currentSpace || holoSphere.currentSpace.exp < Date.now()) {
36
+ await holoSphere.login(testCredentials.spacename, testCredentials.password);
37
+ }
10
38
  });
11
39
 
12
40
  describe('Constructor', () => {
13
- test('should create instance with app name', () => {
41
+ test('should have initialized with correct properties', () => {
14
42
  expect(holoSphere).toBeInstanceOf(HoloSphere);
15
43
  expect(holoSphere.gun).toBeDefined();
16
44
  expect(holoSphere.validator).toBeDefined();
@@ -20,6 +48,8 @@ describe('HoloSphere', () => {
20
48
  test('should initialize with OpenAI when key provided', () => {
21
49
  const hsWithAI = new HoloSphere(testAppName, false, 'fake-key');
22
50
  expect(hsWithAI.openai).toBeDefined();
51
+ // Clean up additional instance
52
+ if (hsWithAI.gun) hsWithAI.gun.off();
23
53
  });
24
54
  });
25
55
 
@@ -34,25 +64,31 @@ describe('HoloSphere', () => {
34
64
  required: ['id', 'data']
35
65
  };
36
66
 
67
+ beforeEach(async () => {
68
+ // Ensure we're logged in before each schema test
69
+ if (!holoSphere.currentSpace || holoSphere.currentSpace.exp < Date.now()) {
70
+ await holoSphere.login(testCredentials.spacename, testCredentials.password);
71
+ }
72
+ });
73
+
37
74
  test('should set and get schema', async () => {
38
75
  await holoSphere.setSchema(testLens, validSchema);
39
-
40
- // Wait for GunDB to process
41
- await new Promise(resolve => setTimeout(resolve, 500));
42
-
43
76
  const retrievedSchema = await holoSphere.getSchema(testLens);
44
77
  expect(retrievedSchema).toBeDefined();
45
78
  expect(retrievedSchema).toEqual(validSchema);
46
- }, 10000);
79
+ });
47
80
 
48
81
  test('should handle invalid schema parameters', async () => {
49
82
  await expect(holoSphere.setSchema(null, null))
50
83
  .rejects.toThrow('setSchema: Missing required parameters');
51
- }, 10000);
84
+ });
52
85
 
53
86
  test('should enforce strict mode schema validation', async () => {
54
87
  const strictHoloSphere = new HoloSphere(testAppName, true);
55
88
 
89
+ // Login to the strict instance
90
+ await strictHoloSphere.login(testCredentials.spacename, testCredentials.password);
91
+
56
92
  const invalidSchema = {
57
93
  type: 'object',
58
94
  properties: {
@@ -62,30 +98,200 @@ describe('HoloSphere', () => {
62
98
 
63
99
  await expect(strictHoloSphere.setSchema(testLens, invalidSchema))
64
100
  .rejects.toThrow();
65
- }, 10000);
101
+
102
+ // Clean up
103
+ await strictHoloSphere.logout();
104
+ });
66
105
 
67
106
  test('should handle schema retrieval for non-existent lens', async () => {
68
107
  const result = await holoSphere.getSchema('nonexistent-lens');
69
108
  expect(result).toBeNull();
70
109
  });
71
110
 
111
+ test('should maintain schema integrity across storage and retrieval', async () => {
112
+ const testLens = 'schemaTestLens';
113
+ const strictHoloSphere = new HoloSphere(testAppName, true);
114
+
115
+ // Login to the strict instance
116
+ await strictHoloSphere.login(testCredentials.spacename, testCredentials.password);
117
+
118
+ // Create test schemas of increasing complexity
119
+ const testSchemas = [
120
+ {
121
+ type: 'object',
122
+ properties: {
123
+ id: { type: 'string' },
124
+ data: { type: 'string' }
125
+ },
126
+ required: ['id', 'data']
127
+ },
128
+ {
129
+ type: 'object',
130
+ properties: {
131
+ id: { type: 'string' },
132
+ data: { type: 'object' },
133
+ metadata: {
134
+ type: 'object',
135
+ properties: {
136
+ timestamp: { type: 'number' },
137
+ tags: {
138
+ type: 'array',
139
+ items: { type: 'string' }
140
+ }
141
+ }
142
+ }
143
+ },
144
+ required: ['id', 'data']
145
+ }
146
+ ];
147
+
148
+ for (let i = 0; i < testSchemas.length; i++) {
149
+ const testLensWithIndex = `${testLens}_${i}`;
150
+ const schema = testSchemas[i];
151
+
152
+ // Store schema
153
+ await strictHoloSphere.setSchema(testLensWithIndex, schema);
154
+
155
+
156
+ // Retrieve schema
157
+ const retrievedSchema = await strictHoloSphere.getSchema(testLensWithIndex);
158
+
159
+ // Verify schema is retrieved correctly
160
+ expect(retrievedSchema).toBeDefined();
161
+ expect(retrievedSchema).toEqual(schema);
162
+
163
+ // Test schema validation with valid data
164
+ const validData = {
165
+ id: 'test1',
166
+ data: i === 0 ? 'test' : { field: 'value' }
167
+ };
168
+ if (i === 1) {
169
+ validData.metadata = {
170
+ timestamp: Date.now(),
171
+ tags: ['test']
172
+ };
173
+ }
174
+
175
+ // Valid data should work
176
+ await expect(strictHoloSphere.put('testHolon', testLensWithIndex, validData))
177
+ .resolves.toBe(true);
178
+
179
+ // Invalid data should fail in strict mode
180
+ const invalidData = {
181
+ id: 'test2'
182
+ // Missing required 'data' field
183
+ };
184
+
185
+ await expect(strictHoloSphere.put('testHolon', testLensWithIndex, invalidData))
186
+ .rejects.toThrow('Schema validation failed');
187
+
188
+ // Clean up after each schema test
189
+ await strictHoloSphere.deleteAll('testHolon', testLensWithIndex);
190
+ await strictHoloSphere.gun.get(strictHoloSphere.appname)
191
+ .get(testLensWithIndex)
192
+ .get('schema')
193
+ .put(null);
194
+ }
195
+
196
+ // Clean up the strict instance
197
+ if (strictHoloSphere.gun) {
198
+ await strictHoloSphere.logout();
199
+ }
200
+ }, 10000); // Increase timeout to 10 seconds
201
+
202
+ test('should handle concurrent schema operations', async () => {
203
+ const baseLens = 'concurrentSchemaTest';
204
+ const numOperations = 5;
205
+ const promises = [];
206
+ const expectedSchemas = [];
207
+
208
+ // Create and store schemas concurrently
209
+ for (let i = 0; i < numOperations; i++) {
210
+ const lens = `${baseLens}_${i}`;
211
+ const schema = {
212
+ type: 'object',
213
+ properties: {
214
+ id: { type: 'string' },
215
+ value: { type: 'string' }
216
+ },
217
+ required: ['id', 'value']
218
+ };
219
+ expectedSchemas.push({ lens, schema });
220
+ promises.push(holoSphere.setSchema(lens, schema));
221
+ }
222
+
223
+ // Wait for all operations to complete
224
+ await Promise.all(promises);
225
+
226
+
227
+ // Verify each schema was stored correctly
228
+ for (const { lens, schema } of expectedSchemas) {
229
+ const retrievedSchema = await holoSphere.getSchema(lens);
230
+ expect(retrievedSchema).toEqual(schema);
231
+ }
232
+ }, 10000); // Increase timeout to 10 seconds
233
+
234
+ test('should handle schema updates correctly', async () => {
235
+ const testLens = 'schemaUpdateTest';
236
+
237
+ // Initial schema
238
+ const initialSchema = {
239
+ type: 'object',
240
+ properties: {
241
+ id: { type: 'string' },
242
+ data: { type: 'string' }
243
+ },
244
+ required: ['id']
245
+ };
246
+
247
+ // Updated schema
248
+ const updatedSchema = {
249
+ type: 'object',
250
+ properties: {
251
+ id: { type: 'string' },
252
+ data: { type: 'string' },
253
+ metadata: { type: 'object' }
254
+ },
255
+ required: ['id', 'data']
256
+ };
257
+
258
+ // Set initial schema
259
+ await holoSphere.setSchema(testLens, initialSchema);
260
+
261
+ // Verify initial schema
262
+ let retrievedSchema = await holoSphere.getSchema(testLens);
263
+ expect(retrievedSchema).toEqual(initialSchema);
264
+
265
+ // Update schema
266
+ await holoSphere.setSchema(testLens, updatedSchema);
267
+
268
+ // Verify updated schema
269
+ retrievedSchema = await holoSphere.getSchema(testLens);
270
+ expect(retrievedSchema).toEqual(updatedSchema);
271
+ });
272
+
72
273
  afterEach(async () => {
73
274
  // Clean up schemas after each test
74
275
  await holoSphere.gun.get(holoSphere.appname)
75
276
  .get(testLens)
76
277
  .get('schema')
77
278
  .put(null);
78
-
79
- // Wait for GunDB to process
80
- await new Promise(resolve => setTimeout(resolve, 100));
81
279
  });
82
280
  });
83
281
 
84
282
  describe('Data Operations', () => {
85
283
  const testHolon = h3.latLngToCell(40.7128, -74.0060, 7);
86
284
  const testLens = 'testLens';
87
- const validData = { id: 'test123', data: 'test data' };
285
+ const validData = { id: 'test1', data: 'test data' };
88
286
  const invalidData = { id: 'test456', wrongField: 'wrong data' };
287
+ const expectedValidData = {
288
+ ...validData,
289
+ owner: testCredentials.spacename,
290
+ federation: expect.objectContaining({
291
+ origin: testCredentials.spacename,
292
+ timestamp: expect.any(Number)
293
+ })
294
+ };
89
295
 
90
296
  beforeEach(async () => {
91
297
  // Set up schema for validation tests
@@ -93,7 +299,8 @@ describe('HoloSphere', () => {
93
299
  type: 'object',
94
300
  properties: {
95
301
  id: { type: 'string' },
96
- data: { type: 'string' }
302
+ data: { type: 'string' },
303
+ owner: { type: 'string' }
97
304
  },
98
305
  required: ['id', 'data']
99
306
  };
@@ -101,20 +308,22 @@ describe('HoloSphere', () => {
101
308
  });
102
309
 
103
310
  test('should put and get data with schema validation', async () => {
104
- const validData = { id: 'test1', data: 'test data' };
105
- const invalidData = { id: 'test2' }; // Missing required 'data' field
106
-
107
311
  // Test valid data
108
- await holoSphere.put(testHolon, testLens, validData);
312
+ await expect(holoSphere.put(testHolon, testLens, validData))
313
+ .resolves.toBe(true);
109
314
 
110
315
  // Test invalid data
111
- try {
112
- await holoSphere.put(testHolon, testLens, invalidData);
113
- fail('Expected validation error but operation succeeded');
114
- } catch (error) {
115
- expect(error.message).toContain('Schema validation failed');
116
- }
117
- }, 10000);
316
+ await expect(holoSphere.put(testHolon, testLens, invalidData))
317
+ .rejects.toThrow('Schema validation failed');
318
+
319
+ // Verify the valid data was stored correctly
320
+ const result = await holoSphere.get(testHolon, testLens, validData.id);
321
+ expect(result).toEqual(expect.objectContaining(expectedValidData));
322
+
323
+ // Verify the invalid data was not stored
324
+ const invalidResult = await holoSphere.get(testHolon, testLens, invalidData.id);
325
+ expect(invalidResult).toBeNull();
326
+ });
118
327
 
119
328
  test('should get all data with schema validation', async () => {
120
329
  await holoSphere.put(testHolon, testLens, validData);
@@ -124,7 +333,7 @@ describe('HoloSphere', () => {
124
333
  expect(Array.isArray(results)).toBeTruthy();
125
334
  expect(results.length).toBeGreaterThan(0);
126
335
  expect(results.some(item => item.id === validData.id)).toBeTruthy();
127
- }, 10000);
336
+ });
128
337
 
129
338
  test('should delete data', async () => {
130
339
  await holoSphere.put(testHolon, testLens, validData);
@@ -132,7 +341,7 @@ describe('HoloSphere', () => {
132
341
 
133
342
  const result = await holoSphere.get(testHolon, testLens, validData.id);
134
343
  expect(result).toBeNull();
135
- }, 10000);
344
+ });
136
345
 
137
346
  test('should delete all data', async () => {
138
347
  await holoSphere.put(testHolon, testLens, validData);
@@ -143,11 +352,14 @@ describe('HoloSphere', () => {
143
352
 
144
353
  const results = await holoSphere.getAll(testHolon, testLens);
145
354
  expect(results).toEqual([]);
146
- }, 10000);
355
+ });
147
356
 
148
357
  test('should enforce strict mode data validation', async () => {
149
358
  const strictHoloSphere = new HoloSphere(testAppName, true);
150
359
 
360
+ // Login to the strict instance
361
+ await strictHoloSphere.login(testCredentials.spacename, testCredentials.password);
362
+
151
363
  // Define schema for strict mode tests
152
364
  const strictSchema = {
153
365
  type: 'object',
@@ -164,7 +376,169 @@ describe('HoloSphere', () => {
164
376
  // Try to put data without schema in strict mode
165
377
  await expect(strictHoloSphere.put(testHolon, 'no-schema-lens', validData))
166
378
  .rejects.toThrow('Schema required in strict mode');
167
- }, 10000);
379
+
380
+ // Clean up
381
+ await strictHoloSphere.logout();
382
+ });
383
+
384
+ test('should maintain content integrity in holon storage', async () => {
385
+ const testData = [
386
+ { id: 'test1', data: 'content1' },
387
+ { id: 'test2', data: 'content2' },
388
+ { id: 'test3', data: 'content3' }
389
+ ];
390
+
391
+ const expectedData = testData.map(data => ({
392
+ ...data,
393
+ owner: testCredentials.spacename,
394
+ federation: expect.objectContaining({
395
+ origin: testCredentials.spacename,
396
+ timestamp: expect.any(Number)
397
+ })
398
+ }));
399
+
400
+ // Store all test data
401
+ for (const data of testData) {
402
+ await holoSphere.put(testHolon, testLens, data);
403
+ }
404
+
405
+ // Retrieve all data
406
+ const retrievedData = await holoSphere.getAll(testHolon, testLens);
407
+
408
+ // Sort both arrays by id for comparison
409
+ const sortedExpectedData = [...expectedData].sort((a, b) => a.id.localeCompare(b.id));
410
+ const sortedRetrievedData = [...retrievedData].sort((a, b) => a.id.localeCompare(b.id));
411
+
412
+ // Verify no duplicates
413
+ const uniqueIds = new Set(retrievedData.map(item => item.id));
414
+ expect(uniqueIds.size).toBe(testData.length);
415
+
416
+ // Verify all items are present and correct
417
+ expect(sortedRetrievedData).toEqual(expect.arrayContaining(sortedExpectedData));
418
+
419
+ // Verify individual item retrieval
420
+ for (let i = 0; i < testData.length; i++) {
421
+ const item = await holoSphere.get(testHolon, testLens, testData[i].id);
422
+ expect(item).toEqual(expect.objectContaining(expectedData[i]));
423
+ }
424
+ });
425
+
426
+ test('should handle data consistency in get operations', async () => {
427
+ const testData = [
428
+ { id: 'test1', data: 'content1' },
429
+ { id: 'test2', data: 'content2' },
430
+ { id: 'test3', data: 'content3' }
431
+ ].map(data => ({
432
+ ...data,
433
+ owner: testCredentials.spacename,
434
+ federation: expect.objectContaining({
435
+ origin: testCredentials.spacename,
436
+ timestamp: expect.any(Number)
437
+ })
438
+ }));
439
+
440
+ // Store test data
441
+ for (const data of testData) {
442
+ const { federation, ...storeData } = data;
443
+ await holoSphere.put(testHolon, testLens, storeData);
444
+ }
445
+
446
+ // Test individual get operations
447
+ for (const expected of testData) {
448
+ const result = await holoSphere.get(testHolon, testLens, expected.id);
449
+ expect(result).toEqual(expect.objectContaining(expected));
450
+ }
451
+
452
+ // Multiple consecutive gets should return same data
453
+ for (let i = 0; i < 5; i++) {
454
+ const result = await holoSphere.get(testHolon, testLens, 'test1');
455
+ expect(result).toEqual(expect.objectContaining(testData[0]));
456
+ }
457
+ });
458
+
459
+ test('should handle data consistency in getAll operations', async () => {
460
+ const testData = Array.from({ length: 10 }, (_, i) => ({
461
+ id: `test${i}`,
462
+ data: `content${i}`,
463
+ owner: testCredentials.spacename,
464
+ federation: expect.objectContaining({
465
+ origin: testCredentials.spacename,
466
+ timestamp: expect.any(Number)
467
+ })
468
+ }));
469
+
470
+ // Store test data sequentially to ensure consistency
471
+ for (const data of testData) {
472
+ const { federation, ...storeData } = data;
473
+ await holoSphere.put(testHolon, testLens, storeData);
474
+ }
475
+
476
+ // Get data multiple times
477
+ const results = await Promise.all(
478
+ Array.from({ length: 5 }, () => holoSphere.getAll(testHolon, testLens))
479
+ );
480
+
481
+ // Verify results
482
+ results.forEach(result => {
483
+ // Should have correct length
484
+ expect(result.length).toBe(testData.length);
485
+
486
+ // Should have no duplicates
487
+ const ids = result.map(item => item.id);
488
+ const uniqueIds = new Set(ids);
489
+ expect(uniqueIds.size).toBe(testData.length);
490
+
491
+ // Should contain all expected data
492
+ const sortedResult = [...result].sort((a, b) => a.id.localeCompare(b.id));
493
+ const sortedExpected = [...testData].sort((a, b) => a.id.localeCompare(b.id));
494
+ sortedResult.forEach((item, idx) => {
495
+ expect(item).toEqual(expect.objectContaining(sortedExpected[idx]));
496
+ });
497
+ });
498
+ }, 15000);
499
+
500
+ test('should handle rapid concurrent getAll operations', async () => {
501
+ const testData = Array.from({ length: 5 }, (_, i) => ({
502
+ id: `concurrent${i}`,
503
+ data: `data${i}`
504
+ }));
505
+
506
+ // Store test data
507
+ for (const data of testData) {
508
+ await holoSphere.put(testHolon, testLens, data);
509
+ }
510
+
511
+ // Perform multiple concurrent getAll operations
512
+ const promises = Array.from({ length: 10 }, () =>
513
+ holoSphere.getAll(testHolon, testLens)
514
+ );
515
+
516
+ const results = await Promise.all(promises);
517
+
518
+ // Verify each result has the correct number of items
519
+ results.forEach(result => {
520
+ expect(result.length).toBe(testData.length);
521
+
522
+ // Check for duplicates within each result
523
+ const ids = result.map(item => item.id);
524
+ const uniqueIds = new Set(ids);
525
+ expect(uniqueIds.size).toBe(ids.length);
526
+ });
527
+
528
+ // Verify consistency across results
529
+ const sortedResults = results.map(result =>
530
+ [...result].sort((a, b) => a.id.localeCompare(b.id))
531
+ );
532
+
533
+ for (let i = 1; i < sortedResults.length; i++) {
534
+ expect(sortedResults[i]).toEqual(sortedResults[0]);
535
+ }
536
+ });
537
+
538
+ // Add cleanup after each test
539
+ afterEach(async () => {
540
+ await holoSphere.deleteAll(testHolon, testLens);
541
+ });
168
542
  });
169
543
 
170
544
  describe('Node Operations', () => {
@@ -175,18 +549,14 @@ describe('HoloSphere', () => {
175
549
  test('should put and get node', async () => {
176
550
  await holoSphere.putNode(testHolon, testLens, testNode);
177
551
 
178
- // Wait for GunDB to process
179
- await new Promise(resolve => setTimeout(resolve, 100));
180
-
181
552
  const result = await holoSphere.getNode(testHolon, testLens, 'value');
182
553
  expect(result).toBeDefined();
183
554
  expect(result).toBe('test node data');
184
- }, 10000);
555
+ });
185
556
 
186
557
  test('should delete node', async () => {
187
558
  // First put the node
188
559
  await holoSphere.putNode(testHolon, testLens, testNode);
189
- await new Promise(resolve => setTimeout(resolve, 100));
190
560
 
191
561
  // Verify node exists
192
562
  const beforeDelete = await holoSphere.getNode(testHolon, testLens, 'value');
@@ -196,23 +566,19 @@ describe('HoloSphere', () => {
196
566
  const deleteResult = await holoSphere.deleteNode(testHolon, testLens, 'value');
197
567
  expect(deleteResult).toBe(true);
198
568
 
199
- // Wait for deletion to process
200
- await new Promise(resolve => setTimeout(resolve, 100));
201
-
202
569
  // Verify node is deleted
203
570
  const afterDelete = await holoSphere.getNode(testHolon, testLens, 'value');
204
571
  expect(afterDelete).toBeNull();
205
- }, 10000);
572
+ });
206
573
 
207
574
  test('should handle invalid node operations', async () => {
208
575
  await expect(holoSphere.deleteNode(null, null, null))
209
576
  .rejects.toThrow('deleteNode: Missing required parameters');
210
- }, 10000);
577
+ });
211
578
 
212
579
  afterEach(async () => {
213
580
  // Clean up after each test
214
581
  await holoSphere.deleteNode(testHolon, testLens, 'value');
215
- await new Promise(resolve => setTimeout(resolve, 100));
216
582
  });
217
583
  });
218
584
 
@@ -246,74 +612,150 @@ describe('HoloSphere', () => {
246
612
  const testLens = 'testLens';
247
613
 
248
614
  beforeEach(async () => {
249
- // Clear any existing subscriptions and data
250
- holoSphere.cleanup();
251
615
  await holoSphere.deleteAll(testHolon, testLens);
252
- // Wait for cleanup to complete
253
- await new Promise(resolve => setTimeout(resolve, 500));
254
616
  });
255
617
 
256
- test('should subscribe to changes', async () => {
257
- const changes = [];
258
- const subscription = await holoSphere.subscribe(testHolon, testLens, (data) => {
259
- changes.push(data);
260
- });
618
+ test('should receive data through subscription', async () => {
619
+ const testData = { id: 'test1', data: 'test data' };
620
+ const expectedData = {
621
+ ...testData,
622
+ owner: testCredentials.spacename,
623
+ federation: expect.objectContaining({
624
+ origin: testCredentials.spacename,
625
+ timestamp: expect.any(Number)
626
+ })
627
+ };
628
+ let received = false;
629
+
630
+ return new Promise((resolve, reject) => {
631
+ const timeout = setTimeout(() => {
632
+ reject(new Error('Subscription timeout'));
633
+ }, 20000);
634
+
635
+ holoSphere.subscribe(testHolon, testLens, (data) => {
636
+ if (!received && data.id === testData.id) {
637
+ try {
638
+ received = true;
639
+ expect(data).toEqual(expect.objectContaining(expectedData));
640
+ clearTimeout(timeout);
641
+ resolve();
642
+ } catch (error) {
643
+ clearTimeout(timeout);
644
+ reject(error);
645
+ }
646
+ }
647
+ });
261
648
 
262
- expect(subscription.id).toBeDefined();
263
- expect(typeof subscription.unsubscribe).toBe('function');
649
+ // Put data after subscription
650
+ setTimeout(async () => {
651
+ try {
652
+ await holoSphere.put(testHolon, testLens, testData);
653
+ } catch (error) {
654
+ clearTimeout(timeout);
655
+ reject(error);
656
+ }
657
+ }, 1000);
658
+ });
659
+ }, 30000);
660
+
661
+ test('should stop receiving data after unsubscribe', async () => {
662
+ const testData1 = { id: 'test1', data: 'first' };
663
+ const testData2 = { id: 'test2', data: 'second' };
664
+ let receivedData = [];
665
+
666
+ return new Promise(async (resolve, reject) => {
667
+ const timeout = setTimeout(() => {
668
+ // If we only received the first piece of data, test passes
669
+ if (receivedData.length === 1 && receivedData[0].id === testData1.id) {
670
+ resolve();
671
+ } else {
672
+ reject(new Error('Test timeout or received unexpected data'));
673
+ }
674
+ }, 5000);
675
+
676
+ const subscription = await holoSphere.subscribe(testHolon, testLens, async (data) => {
677
+ receivedData.push(data);
678
+
679
+ if (data.id === testData1.id) {
680
+ subscription.unsubscribe();
681
+ resolve();
682
+ } else if (data.id === testData2.id) {
683
+ clearTimeout(timeout);
684
+ reject(new Error('Received data after unsubscribe'));
685
+ }
686
+ });
264
687
 
265
- await holoSphere.put(testHolon, testLens, { id: 'test1', data: 'test data' });
266
-
267
- // Wait longer for subscription to process
268
- await new Promise(resolve => setTimeout(resolve, 500));
269
-
270
- expect(changes.length).toBeGreaterThan(0);
688
+ // Put first piece of data
689
+ await holoSphere.put(testHolon, testLens, testData1);
690
+ });
271
691
  }, 10000);
272
692
 
273
- test('should unsubscribe properly', async () => {
274
- const changes = [];
275
- let subscription;
693
+ test('should handle multiple subscriptions', async () => {
694
+ const testData = { id: 'test1', data: 'test data' };
695
+ const expectedData = {
696
+ ...testData,
697
+ owner: testCredentials.spacename,
698
+ federation: expect.objectContaining({
699
+ origin: testCredentials.spacename,
700
+ timestamp: expect.any(Number)
701
+ })
702
+ };
703
+ let received1 = false;
704
+ let received2 = false;
705
+
706
+ return new Promise((resolve, reject) => {
707
+ const timeout = setTimeout(() => {
708
+ reject(new Error('Subscription timeout'));
709
+ }, 20000);
710
+
711
+ function checkDone() {
712
+ if (received1 && received2) {
713
+ clearTimeout(timeout);
714
+ resolve();
715
+ }
716
+ }
717
+
718
+ holoSphere.subscribe(testHolon, testLens, (data) => {
719
+ if (data.id === testData.id) {
720
+ try {
721
+ received1 = true;
722
+ expect(data).toEqual(expect.objectContaining(expectedData));
723
+ checkDone();
724
+ } catch (error) {
725
+ clearTimeout(timeout);
726
+ reject(error);
727
+ }
728
+ }
729
+ });
276
730
 
277
- // Create a promise that resolves after receiving the first change
278
- const firstChangePromise = new Promise(resolve => {
279
- subscription = holoSphere.subscribe(testHolon, testLens, (data) => {
280
- changes.push(data);
281
- if (changes.length === 1) resolve();
731
+ holoSphere.subscribe(testHolon, testLens, (data) => {
732
+ if (data.id === testData.id) {
733
+ try {
734
+ received2 = true;
735
+ expect(data).toEqual(expect.objectContaining(expectedData));
736
+ checkDone();
737
+ } catch (error) {
738
+ clearTimeout(timeout);
739
+ reject(error);
740
+ }
741
+ }
282
742
  });
743
+
744
+ // Put data after both subscriptions
745
+ setTimeout(async () => {
746
+ try {
747
+ await holoSphere.put(testHolon, testLens, testData);
748
+ } catch (error) {
749
+ clearTimeout(timeout);
750
+ reject(error);
751
+ }
752
+ }, 1000);
283
753
  });
754
+ }, 30000);
284
755
 
285
- // Put initial data and wait for first change
286
- await holoSphere.put(testHolon, testLens, { id: 'test1', data: 'test data' });
287
- await firstChangePromise;
288
-
289
- // Clear changes and unsubscribe
290
- changes.length = 0;
291
- subscription.unsubscribe();
292
-
293
- // Wait longer for unsubscribe to take effect
294
- await new Promise(resolve => setTimeout(resolve, 1000));
295
-
296
- // Put new data
297
- await holoSphere.put(testHolon, testLens, { id: 'test2', data: 'new data' });
298
-
299
- // Wait to ensure no new changes are received
300
- await new Promise(resolve => setTimeout(resolve, 1000));
301
-
302
- expect(changes.length).toBe(0);
303
- }, 20000);
304
-
305
- test('should cleanup all subscriptions', async () => {
306
- const subs = [];
307
- for (let i = 0; i < 3; i++) {
308
- subs.push(await holoSphere.subscribe(testHolon, testLens, () => {}));
309
- // Wait between subscriptions
310
- await new Promise(resolve => setTimeout(resolve, 100));
311
- }
312
-
313
- expect(holoSphere.subscriptions.size).toBe(3);
314
- holoSphere.cleanup();
315
- expect(holoSphere.subscriptions.size).toBe(0);
316
- }, 10000);
756
+ afterEach(async () => {
757
+ await holoSphere.deleteAll(testHolon, testLens);
758
+ });
317
759
  });
318
760
 
319
761
  describe('Parse Operations', () => {
@@ -350,6 +792,81 @@ describe('HoloSphere', () => {
350
792
  expect(Array.isArray(results)).toBe(true);
351
793
  expect(results.length).toBeGreaterThan(0);
352
794
  });
795
+
796
+ test('should maintain content integrity in global storage', async () => {
797
+ const testTable = 'testGlobalTable';
798
+
799
+ // Create test data with unique IDs and content
800
+ const testData = [
801
+ { id: 'global1', value: 'value1' },
802
+ { id: 'global2', value: 'value2' },
803
+ { id: 'global3', value: 'value3' }
804
+ ];
805
+
806
+ // Store all test data
807
+ for (const data of testData) {
808
+ await holoSphere.putGlobal(testTable, data);
809
+ }
810
+
811
+ // Retrieve all data
812
+ const retrievedData = await holoSphere.getAllGlobal(testTable);
813
+
814
+ // Sort both arrays by id for comparison
815
+ const sortedTestData = [...testData].sort((a, b) => a.id.localeCompare(b.id));
816
+ const sortedRetrievedData = [...retrievedData].sort((a, b) => a.id.localeCompare(b.id));
817
+
818
+ // Verify no duplicates
819
+ const uniqueIds = new Set(retrievedData.map(item => item.id));
820
+ expect(uniqueIds.size).toBe(testData.length);
821
+
822
+ // Verify all items are present and correct
823
+ expect(sortedRetrievedData).toEqual(sortedTestData);
824
+
825
+ // Verify individual item retrieval
826
+ for (const data of testData) {
827
+ const item = await holoSphere.getGlobal(testTable, data.id);
828
+ expect(item).toEqual(data);
829
+ }
830
+
831
+ // Clean up test data
832
+ await holoSphere.deleteAllGlobal(testTable);
833
+ });
834
+
835
+ test('should handle concurrent global operations without data corruption', async () => {
836
+ const testTable = 'concurrentGlobalTest';
837
+ const numOperations = 5;
838
+ const promises = [];
839
+ const expectedData = [];
840
+
841
+ // Create and store data concurrently with small delays
842
+ for (let i = 0; i < numOperations; i++) {
843
+ const data = { id: `concurrent${i}`, value: `value${i}` };
844
+ expectedData.push(data);
845
+ promises.push(holoSphere.putGlobal(testTable, data));
846
+ }
847
+
848
+ // Wait for all operations to complete
849
+ await Promise.all(promises);
850
+
851
+
852
+
853
+ // Retrieve and verify data
854
+ const retrievedData = await holoSphere.getAllGlobal(testTable);
855
+
856
+ // Sort both arrays by id for comparison
857
+ const sortedExpectedData = [...expectedData].sort((a, b) => a.id.localeCompare(b.id));
858
+ const sortedRetrievedData = [...retrievedData].sort((a, b) => a.id.localeCompare(b.id));
859
+
860
+ // Verify no duplicates
861
+ const uniqueIds = new Set(retrievedData.map(item => item.id));
862
+ expect(uniqueIds.size).toBe(numOperations);
863
+
864
+ // Verify all items are present and correct
865
+ expect(sortedRetrievedData).toEqual(sortedExpectedData);
866
+
867
+ // Clean up test data
868
+ await holoSphere.deleteAllGlobal(testTable);
869
+ }, 15000); // Increase timeout to 15 seconds
353
870
  });
354
871
 
355
872
  describe('Compute Operations', () => {
@@ -363,34 +880,62 @@ describe('HoloSphere', () => {
363
880
  properties: {
364
881
  id: { type: 'string' },
365
882
  content: { type: 'string' },
366
- data: { type: 'string' }
883
+ timestamp: { type: 'number' }
367
884
  },
368
- required: ['id', 'data']
885
+ required: ['id', 'content']
369
886
  };
370
887
  await holoSphere.setSchema(testLens, schema);
371
888
  });
372
889
 
373
- test('should validate compute parameters', async () => {
374
- await expect(holoSphere.compute(null, null))
890
+ test('should validate required parameters', async () => {
891
+ await expect(holoSphere.compute(null, null, null))
892
+ .rejects.toThrow('compute: Missing required parameters');
893
+
894
+ await expect(holoSphere.compute(testHolon, null, null))
895
+ .rejects.toThrow('compute: Missing required parameters');
896
+
897
+ await expect(holoSphere.compute(testHolon, testLens, null))
375
898
  .rejects.toThrow('compute: Missing required parameters');
376
899
  });
377
900
 
901
+
378
902
  test('should validate holon resolution', async () => {
379
- const invalidHolon = 'invalid';
380
- await expect(holoSphere.compute(invalidHolon, 'testLens'))
381
- .rejects.toThrow('compute: Invalid holon resolution');
903
+ const invalidHolon = h3.latLngToCell(40.7128, -74.0060, 0); // Resolution 0
904
+ await expect(holoSphere.compute(invalidHolon, testLens, { operation: 'summarize' }))
905
+ .rejects.toThrow('compute: Invalid holon resolution (must be between 1 and 15)');
382
906
  });
383
907
 
384
- test('should compute with valid parameters', async () => {
385
- await holoSphere.put(testHolon, testLens, {
386
- id: 'test1',
387
- data: 'test data',
388
- content: 'test content'
389
- });
390
-
391
- await expect(holoSphere.compute(testHolon, testLens, 'summarize'))
392
- .resolves.not.toThrow();
393
- }, 15000);
908
+ test('should validate depth parameters', async () => {
909
+ await expect(holoSphere.compute(testHolon, testLens, {
910
+ operation: 'summarize',
911
+ depth: -1
912
+ })).rejects.toThrow('compute: Invalid depth parameter');
913
+
914
+ await expect(holoSphere.compute(testHolon, testLens, {
915
+ operation: 'summarize',
916
+ maxDepth: 0
917
+ })).rejects.toThrow('compute: Invalid maxDepth parameter (must be between 1 and 15)');
918
+
919
+ await expect(holoSphere.compute(testHolon, testLens, {
920
+ operation: 'summarize',
921
+ maxDepth: 16
922
+ })).rejects.toThrow('compute: Invalid maxDepth parameter (must be between 1 and 15)');
923
+ });
924
+
925
+ test('should validate operation type', async () => {
926
+ await expect(holoSphere.compute(testHolon, testLens, {
927
+ operation: 'invalid-operation'
928
+ })).rejects.toThrow('compute: Invalid operation (must be one of summarize, aggregate, concatenate)');
929
+ });
930
+
931
+ afterEach(async () => {
932
+ // Clean up test data
933
+ await holoSphere.deleteAll(testHolon, testLens);
934
+ await holoSphere.gun.get(holoSphere.appname)
935
+ .get(testLens)
936
+ .get('schema')
937
+ .put(null);
938
+ });
394
939
  });
395
940
 
396
941
  describe('Error Handling', () => {
@@ -400,7 +945,6 @@ describe('HoloSphere', () => {
400
945
  beforeEach(async () => {
401
946
  // Clear all existing data
402
947
  await holoSphere.deleteAll(testHolon, testLens);
403
- await new Promise(resolve => setTimeout(resolve, 1000));
404
948
 
405
949
  // Set up fresh schema
406
950
  const schema = {
@@ -412,7 +956,6 @@ describe('HoloSphere', () => {
412
956
  required: ['id', 'data']
413
957
  };
414
958
  await holoSphere.setSchema(testLens, schema);
415
- await new Promise(resolve => setTimeout(resolve, 500));
416
959
  });
417
960
 
418
961
  test('should handle concurrent operations', async () => {
@@ -420,7 +963,7 @@ describe('HoloSphere', () => {
420
963
  const promises = [];
421
964
  const expectedIds = new Set();
422
965
 
423
- // Create concurrent put operations
966
+ // Create concurrent put operations with small delays between them
424
967
  for (let i = 0; i < numOperations; i++) {
425
968
  const id = `concurrent${i}`;
426
969
  expectedIds.add(id);
@@ -432,7 +975,6 @@ describe('HoloSphere', () => {
432
975
 
433
976
  // Wait for all operations to complete
434
977
  await Promise.all(promises);
435
- await new Promise(resolve => setTimeout(resolve, 1000));
436
978
 
437
979
  // Get and verify results
438
980
  const results = await holoSphere.getAll(testHolon, testLens);
@@ -445,7 +987,7 @@ describe('HoloSphere', () => {
445
987
  expectedIds.forEach(id => {
446
988
  expect(resultIds.has(id)).toBe(true);
447
989
  });
448
- }, 20000);
990
+ }, 15000); // Increase timeout to 15 seconds
449
991
 
450
992
  test('should handle large data sets', async () => {
451
993
  const largeData = {
@@ -456,9 +998,6 @@ describe('HoloSphere', () => {
456
998
  // Put the data
457
999
  await holoSphere.put(testHolon, testLens, largeData);
458
1000
 
459
- // Wait for data to be stored
460
- await new Promise(resolve => setTimeout(resolve, 1000));
461
-
462
1001
  // Get the data back
463
1002
  const result = await holoSphere.get(testHolon, testLens, 'large');
464
1003
 
@@ -466,12 +1005,11 @@ describe('HoloSphere', () => {
466
1005
  expect(result).toBeDefined();
467
1006
  expect(result.id).toBe(largeData.id);
468
1007
  expect(result.data).toBe(largeData.data);
469
- }, 20000);
1008
+ });
470
1009
 
471
1010
  afterEach(async () => {
472
1011
  // Clean up after each test
473
1012
  await holoSphere.deleteAll(testHolon, testLens);
474
- await new Promise(resolve => setTimeout(resolve, 1000));
475
1013
  });
476
1014
  });
477
1015
 
@@ -492,11 +1030,21 @@ describe('HoloSphere', () => {
492
1030
 
493
1031
  afterAll(async () => {
494
1032
  // Clean up test data
495
- const testLens = 'testLens';
496
- const testHolon = h3.latLngToCell(40.7128, -74.0060, 7);
497
- await holoSphere.deleteAll(testHolon, testLens);
498
-
499
- // Allow time for Gun to process
500
- await new Promise(resolve => setTimeout(resolve, 1000));
1033
+ const testLens = 'testLens';
1034
+ const testHolon = h3.latLngToCell(40.7128, -74.0060, 7);
1035
+ await holoSphere.deleteAll(testHolon, testLens);
1036
+
1037
+ // Clean up test tables
1038
+ await holoSphere.deleteAllGlobal('testTable');
1039
+ await holoSphere.deleteAllGlobal('testGlobalTable');
1040
+ await holoSphere.deleteAllGlobal('concurrentGlobalTest');
1041
+
1042
+ // Clean up test space
1043
+ await holoSphere.deleteGlobal('spaces', testCredentials.spacename);
1044
+
1045
+ // Logout
1046
+ if (holoSphere.currentSpace) {
1047
+ await holoSphere.logout();
1048
+ }
501
1049
  });
502
1050
  });