holosphere 1.1.2 → 1.1.3

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,207 @@ 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
+ // Add delay to ensure schema is stored
156
+ await new Promise(resolve => setTimeout(resolve, 100));
157
+
158
+ // Retrieve schema
159
+ const retrievedSchema = await strictHoloSphere.getSchema(testLensWithIndex);
160
+
161
+ // Verify schema is retrieved correctly
162
+ expect(retrievedSchema).toBeDefined();
163
+ expect(retrievedSchema).toEqual(schema);
164
+
165
+ // Test schema validation with valid data
166
+ const validData = {
167
+ id: 'test1',
168
+ data: i === 0 ? 'test' : { field: 'value' }
169
+ };
170
+ if (i === 1) {
171
+ validData.metadata = {
172
+ timestamp: Date.now(),
173
+ tags: ['test']
174
+ };
175
+ }
176
+
177
+ // Valid data should work
178
+ await expect(strictHoloSphere.put('testHolon', testLensWithIndex, validData))
179
+ .resolves.toBe(true);
180
+
181
+ // Invalid data should fail in strict mode
182
+ const invalidData = {
183
+ id: 'test2'
184
+ // Missing required 'data' field
185
+ };
186
+
187
+ await expect(strictHoloSphere.put('testHolon', testLensWithIndex, invalidData))
188
+ .rejects.toThrow('Schema validation failed');
189
+
190
+ // Clean up after each schema test
191
+ await strictHoloSphere.deleteAll('testHolon', testLensWithIndex);
192
+ await strictHoloSphere.gun.get(strictHoloSphere.appname)
193
+ .get(testLensWithIndex)
194
+ .get('schema')
195
+ .put(null);
196
+ }
197
+
198
+ // Clean up the strict instance
199
+ if (strictHoloSphere.gun) {
200
+ await strictHoloSphere.logout();
201
+ await new Promise(resolve => setTimeout(resolve, 100)); // Allow time for cleanup
202
+ }
203
+ }, 10000); // Increase timeout to 10 seconds
204
+
205
+ test('should handle concurrent schema operations', async () => {
206
+ const baseLens = 'concurrentSchemaTest';
207
+ const numOperations = 5;
208
+ const promises = [];
209
+ const expectedSchemas = [];
210
+
211
+ // Create and store schemas concurrently
212
+ for (let i = 0; i < numOperations; i++) {
213
+ const lens = `${baseLens}_${i}`;
214
+ const schema = {
215
+ type: 'object',
216
+ properties: {
217
+ id: { type: 'string' },
218
+ value: { type: 'string' }
219
+ },
220
+ required: ['id', 'value']
221
+ };
222
+ expectedSchemas.push({ lens, schema });
223
+ // Add small delay between operations to prevent race conditions
224
+ await new Promise(resolve => setTimeout(resolve, 50));
225
+ promises.push(holoSphere.setSchema(lens, schema));
226
+ }
227
+
228
+ // Wait for all operations to complete
229
+ await Promise.all(promises);
230
+
231
+ // Add delay before verification to ensure data is settled
232
+ await new Promise(resolve => setTimeout(resolve, 500));
233
+
234
+ // Verify each schema was stored correctly
235
+ for (const { lens, schema } of expectedSchemas) {
236
+ const retrievedSchema = await holoSphere.getSchema(lens);
237
+ expect(retrievedSchema).toEqual(schema);
238
+ }
239
+ }, 10000); // Increase timeout to 10 seconds
240
+
241
+ test('should handle schema updates correctly', async () => {
242
+ const testLens = 'schemaUpdateTest';
243
+
244
+ // Initial schema
245
+ const initialSchema = {
246
+ type: 'object',
247
+ properties: {
248
+ id: { type: 'string' },
249
+ data: { type: 'string' }
250
+ },
251
+ required: ['id']
252
+ };
253
+
254
+ // Updated schema
255
+ const updatedSchema = {
256
+ type: 'object',
257
+ properties: {
258
+ id: { type: 'string' },
259
+ data: { type: 'string' },
260
+ metadata: { type: 'object' }
261
+ },
262
+ required: ['id', 'data']
263
+ };
264
+
265
+ // Set initial schema
266
+ await holoSphere.setSchema(testLens, initialSchema);
267
+
268
+ // Verify initial schema
269
+ let retrievedSchema = await holoSphere.getSchema(testLens);
270
+ expect(retrievedSchema).toEqual(initialSchema);
271
+
272
+ // Update schema
273
+ await holoSphere.setSchema(testLens, updatedSchema);
274
+
275
+ // Verify updated schema
276
+ retrievedSchema = await holoSphere.getSchema(testLens);
277
+ expect(retrievedSchema).toEqual(updatedSchema);
278
+ });
279
+
72
280
  afterEach(async () => {
73
281
  // Clean up schemas after each test
74
282
  await holoSphere.gun.get(holoSphere.appname)
75
283
  .get(testLens)
76
284
  .get('schema')
77
285
  .put(null);
78
-
79
- // Wait for GunDB to process
80
- await new Promise(resolve => setTimeout(resolve, 100));
81
286
  });
82
287
  });
83
288
 
84
289
  describe('Data Operations', () => {
85
290
  const testHolon = h3.latLngToCell(40.7128, -74.0060, 7);
86
291
  const testLens = 'testLens';
87
- const validData = { id: 'test123', data: 'test data' };
292
+ const validData = { id: 'test1', data: 'test data' };
88
293
  const invalidData = { id: 'test456', wrongField: 'wrong data' };
294
+ const expectedValidData = {
295
+ ...validData,
296
+ owner: testCredentials.spacename,
297
+ federation: expect.objectContaining({
298
+ origin: testCredentials.spacename,
299
+ timestamp: expect.any(Number)
300
+ })
301
+ };
89
302
 
90
303
  beforeEach(async () => {
91
304
  // Set up schema for validation tests
@@ -93,7 +306,8 @@ describe('HoloSphere', () => {
93
306
  type: 'object',
94
307
  properties: {
95
308
  id: { type: 'string' },
96
- data: { type: 'string' }
309
+ data: { type: 'string' },
310
+ owner: { type: 'string' }
97
311
  },
98
312
  required: ['id', 'data']
99
313
  };
@@ -101,20 +315,22 @@ describe('HoloSphere', () => {
101
315
  });
102
316
 
103
317
  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
318
  // Test valid data
108
- await holoSphere.put(testHolon, testLens, validData);
319
+ await expect(holoSphere.put(testHolon, testLens, validData))
320
+ .resolves.toBe(true);
109
321
 
110
322
  // 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);
323
+ await expect(holoSphere.put(testHolon, testLens, invalidData))
324
+ .rejects.toThrow('Schema validation failed');
325
+
326
+ // Verify the valid data was stored correctly
327
+ const result = await holoSphere.get(testHolon, testLens, validData.id);
328
+ expect(result).toEqual(expect.objectContaining(expectedValidData));
329
+
330
+ // Verify the invalid data was not stored
331
+ const invalidResult = await holoSphere.get(testHolon, testLens, invalidData.id);
332
+ expect(invalidResult).toBeNull();
333
+ });
118
334
 
119
335
  test('should get all data with schema validation', async () => {
120
336
  await holoSphere.put(testHolon, testLens, validData);
@@ -124,7 +340,7 @@ describe('HoloSphere', () => {
124
340
  expect(Array.isArray(results)).toBeTruthy();
125
341
  expect(results.length).toBeGreaterThan(0);
126
342
  expect(results.some(item => item.id === validData.id)).toBeTruthy();
127
- }, 10000);
343
+ });
128
344
 
129
345
  test('should delete data', async () => {
130
346
  await holoSphere.put(testHolon, testLens, validData);
@@ -132,7 +348,7 @@ describe('HoloSphere', () => {
132
348
 
133
349
  const result = await holoSphere.get(testHolon, testLens, validData.id);
134
350
  expect(result).toBeNull();
135
- }, 10000);
351
+ });
136
352
 
137
353
  test('should delete all data', async () => {
138
354
  await holoSphere.put(testHolon, testLens, validData);
@@ -143,11 +359,14 @@ describe('HoloSphere', () => {
143
359
 
144
360
  const results = await holoSphere.getAll(testHolon, testLens);
145
361
  expect(results).toEqual([]);
146
- }, 10000);
362
+ });
147
363
 
148
364
  test('should enforce strict mode data validation', async () => {
149
365
  const strictHoloSphere = new HoloSphere(testAppName, true);
150
366
 
367
+ // Login to the strict instance
368
+ await strictHoloSphere.login(testCredentials.spacename, testCredentials.password);
369
+
151
370
  // Define schema for strict mode tests
152
371
  const strictSchema = {
153
372
  type: 'object',
@@ -164,7 +383,172 @@ describe('HoloSphere', () => {
164
383
  // Try to put data without schema in strict mode
165
384
  await expect(strictHoloSphere.put(testHolon, 'no-schema-lens', validData))
166
385
  .rejects.toThrow('Schema required in strict mode');
167
- }, 10000);
386
+
387
+ // Clean up
388
+ await strictHoloSphere.logout();
389
+ });
390
+
391
+ test('should maintain content integrity in holon storage', async () => {
392
+ const testData = [
393
+ { id: 'test1', data: 'content1' },
394
+ { id: 'test2', data: 'content2' },
395
+ { id: 'test3', data: 'content3' }
396
+ ];
397
+
398
+ const expectedData = testData.map(data => ({
399
+ ...data,
400
+ owner: testCredentials.spacename,
401
+ federation: expect.objectContaining({
402
+ origin: testCredentials.spacename,
403
+ timestamp: expect.any(Number)
404
+ })
405
+ }));
406
+
407
+ // Store all test data
408
+ for (const data of testData) {
409
+ await holoSphere.put(testHolon, testLens, data);
410
+ }
411
+
412
+ // Retrieve all data
413
+ const retrievedData = await holoSphere.getAll(testHolon, testLens);
414
+
415
+ // Sort both arrays by id for comparison
416
+ const sortedExpectedData = [...expectedData].sort((a, b) => a.id.localeCompare(b.id));
417
+ const sortedRetrievedData = [...retrievedData].sort((a, b) => a.id.localeCompare(b.id));
418
+
419
+ // Verify no duplicates
420
+ const uniqueIds = new Set(retrievedData.map(item => item.id));
421
+ expect(uniqueIds.size).toBe(testData.length);
422
+
423
+ // Verify all items are present and correct
424
+ expect(sortedRetrievedData).toEqual(expect.arrayContaining(sortedExpectedData));
425
+
426
+ // Verify individual item retrieval
427
+ for (let i = 0; i < testData.length; i++) {
428
+ const item = await holoSphere.get(testHolon, testLens, testData[i].id);
429
+ expect(item).toEqual(expect.objectContaining(expectedData[i]));
430
+ }
431
+ });
432
+
433
+ test('should handle data consistency in get operations', async () => {
434
+ const testData = [
435
+ { id: 'test1', data: 'content1' },
436
+ { id: 'test2', data: 'content2' },
437
+ { id: 'test3', data: 'content3' }
438
+ ].map(data => ({
439
+ ...data,
440
+ owner: testCredentials.spacename,
441
+ federation: expect.objectContaining({
442
+ origin: testCredentials.spacename,
443
+ timestamp: expect.any(Number)
444
+ })
445
+ }));
446
+
447
+ // Store test data
448
+ for (const data of testData) {
449
+ const { federation, ...storeData } = data;
450
+ await holoSphere.put(testHolon, testLens, storeData);
451
+ }
452
+
453
+ // Test individual get operations
454
+ for (const expected of testData) {
455
+ const result = await holoSphere.get(testHolon, testLens, expected.id);
456
+ expect(result).toEqual(expect.objectContaining(expected));
457
+ }
458
+
459
+ // Multiple consecutive gets should return same data
460
+ for (let i = 0; i < 5; i++) {
461
+ const result = await holoSphere.get(testHolon, testLens, 'test1');
462
+ expect(result).toEqual(expect.objectContaining(testData[0]));
463
+ }
464
+ });
465
+
466
+ test('should handle data consistency in getAll operations', async () => {
467
+ const testData = Array.from({ length: 10 }, (_, i) => ({
468
+ id: `test${i}`,
469
+ data: `content${i}`,
470
+ owner: testCredentials.spacename,
471
+ federation: expect.objectContaining({
472
+ origin: testCredentials.spacename,
473
+ timestamp: expect.any(Number)
474
+ })
475
+ }));
476
+
477
+ // Store test data sequentially to ensure consistency
478
+ for (const data of testData) {
479
+ const { federation, ...storeData } = data;
480
+ await holoSphere.put(testHolon, testLens, storeData);
481
+ }
482
+
483
+ // Wait a bit to ensure data is settled
484
+ await new Promise(resolve => setTimeout(resolve, 100));
485
+
486
+ // Get data multiple times
487
+ const results = await Promise.all(
488
+ Array.from({ length: 5 }, () => holoSphere.getAll(testHolon, testLens))
489
+ );
490
+
491
+ // Verify results
492
+ results.forEach(result => {
493
+ // Should have correct length
494
+ expect(result.length).toBe(testData.length);
495
+
496
+ // Should have no duplicates
497
+ const ids = result.map(item => item.id);
498
+ const uniqueIds = new Set(ids);
499
+ expect(uniqueIds.size).toBe(testData.length);
500
+
501
+ // Should contain all expected data
502
+ const sortedResult = [...result].sort((a, b) => a.id.localeCompare(b.id));
503
+ const sortedExpected = [...testData].sort((a, b) => a.id.localeCompare(b.id));
504
+ sortedResult.forEach((item, idx) => {
505
+ expect(item).toEqual(expect.objectContaining(sortedExpected[idx]));
506
+ });
507
+ });
508
+ }, 15000);
509
+
510
+ test('should handle rapid concurrent getAll operations', async () => {
511
+ const testData = Array.from({ length: 5 }, (_, i) => ({
512
+ id: `concurrent${i}`,
513
+ data: `data${i}`
514
+ }));
515
+
516
+ // Store test data
517
+ for (const data of testData) {
518
+ await holoSphere.put(testHolon, testLens, data);
519
+ }
520
+
521
+ // Perform multiple concurrent getAll operations
522
+ const promises = Array.from({ length: 10 }, () =>
523
+ holoSphere.getAll(testHolon, testLens)
524
+ );
525
+
526
+ const results = await Promise.all(promises);
527
+
528
+ // Verify each result has the correct number of items
529
+ results.forEach(result => {
530
+ expect(result.length).toBe(testData.length);
531
+
532
+ // Check for duplicates within each result
533
+ const ids = result.map(item => item.id);
534
+ const uniqueIds = new Set(ids);
535
+ expect(uniqueIds.size).toBe(ids.length);
536
+ });
537
+
538
+ // Verify consistency across results
539
+ const sortedResults = results.map(result =>
540
+ [...result].sort((a, b) => a.id.localeCompare(b.id))
541
+ );
542
+
543
+ for (let i = 1; i < sortedResults.length; i++) {
544
+ expect(sortedResults[i]).toEqual(sortedResults[0]);
545
+ }
546
+ });
547
+
548
+ // Add cleanup after each test
549
+ afterEach(async () => {
550
+ await holoSphere.deleteAll(testHolon, testLens);
551
+ });
168
552
  });
169
553
 
170
554
  describe('Node Operations', () => {
@@ -175,18 +559,14 @@ describe('HoloSphere', () => {
175
559
  test('should put and get node', async () => {
176
560
  await holoSphere.putNode(testHolon, testLens, testNode);
177
561
 
178
- // Wait for GunDB to process
179
- await new Promise(resolve => setTimeout(resolve, 100));
180
-
181
562
  const result = await holoSphere.getNode(testHolon, testLens, 'value');
182
563
  expect(result).toBeDefined();
183
564
  expect(result).toBe('test node data');
184
- }, 10000);
565
+ });
185
566
 
186
567
  test('should delete node', async () => {
187
568
  // First put the node
188
569
  await holoSphere.putNode(testHolon, testLens, testNode);
189
- await new Promise(resolve => setTimeout(resolve, 100));
190
570
 
191
571
  // Verify node exists
192
572
  const beforeDelete = await holoSphere.getNode(testHolon, testLens, 'value');
@@ -196,23 +576,19 @@ describe('HoloSphere', () => {
196
576
  const deleteResult = await holoSphere.deleteNode(testHolon, testLens, 'value');
197
577
  expect(deleteResult).toBe(true);
198
578
 
199
- // Wait for deletion to process
200
- await new Promise(resolve => setTimeout(resolve, 100));
201
-
202
579
  // Verify node is deleted
203
580
  const afterDelete = await holoSphere.getNode(testHolon, testLens, 'value');
204
581
  expect(afterDelete).toBeNull();
205
- }, 10000);
582
+ });
206
583
 
207
584
  test('should handle invalid node operations', async () => {
208
585
  await expect(holoSphere.deleteNode(null, null, null))
209
586
  .rejects.toThrow('deleteNode: Missing required parameters');
210
- }, 10000);
587
+ });
211
588
 
212
589
  afterEach(async () => {
213
590
  // Clean up after each test
214
591
  await holoSphere.deleteNode(testHolon, testLens, 'value');
215
- await new Promise(resolve => setTimeout(resolve, 100));
216
592
  });
217
593
  });
218
594
 
@@ -246,74 +622,144 @@ describe('HoloSphere', () => {
246
622
  const testLens = 'testLens';
247
623
 
248
624
  beforeEach(async () => {
249
- // Clear any existing subscriptions and data
250
- holoSphere.cleanup();
251
625
  await holoSphere.deleteAll(testHolon, testLens);
252
- // Wait for cleanup to complete
253
- await new Promise(resolve => setTimeout(resolve, 500));
254
626
  });
255
627
 
256
- test('should subscribe to changes', async () => {
257
- const changes = [];
258
- const subscription = await holoSphere.subscribe(testHolon, testLens, (data) => {
259
- changes.push(data);
260
- });
628
+ test('should receive data through subscription', async () => {
629
+ const testData = { id: 'test1', data: 'test data' };
630
+ const expectedData = {
631
+ ...testData,
632
+ owner: testCredentials.spacename,
633
+ federation: expect.objectContaining({
634
+ origin: testCredentials.spacename,
635
+ timestamp: expect.any(Number)
636
+ })
637
+ };
638
+ let received = false;
639
+
640
+ return new Promise((resolve, reject) => {
641
+ const timeout = setTimeout(() => {
642
+ reject(new Error('Subscription timeout'));
643
+ }, 20000);
644
+
645
+ holoSphere.subscribe(testHolon, testLens, (data) => {
646
+ if (!received && data.id === testData.id) {
647
+ try {
648
+ received = true;
649
+ expect(data).toEqual(expect.objectContaining(expectedData));
650
+ clearTimeout(timeout);
651
+ resolve();
652
+ } catch (error) {
653
+ clearTimeout(timeout);
654
+ reject(error);
655
+ }
656
+ }
657
+ });
261
658
 
262
- expect(subscription.id).toBeDefined();
263
- expect(typeof subscription.unsubscribe).toBe('function');
659
+ // Put data after subscription
660
+ setTimeout(async () => {
661
+ try {
662
+ await holoSphere.put(testHolon, testLens, testData);
663
+ } catch (error) {
664
+ clearTimeout(timeout);
665
+ reject(error);
666
+ }
667
+ }, 1000);
668
+ });
669
+ }, 30000);
670
+
671
+ test('should stop receiving data after unsubscribe', async () => {
672
+ const testData1 = { id: 'test1', data: 'first' };
673
+ const testData2 = { id: 'test2', data: 'second' };
674
+ let received = false;
675
+
676
+ const subscription = await holoSphere.subscribe(testHolon, testLens, async (data) => {
677
+ if (!received && data.id === testData1.id) {
678
+ received = true;
679
+ await subscription.off();
680
+
681
+ // Put second piece of data after unsubscribe
682
+ await holoSphere.put(testHolon, testLens, testData2);
683
+
684
+ // Wait a bit to ensure no more data is received
685
+ setTimeout(() => {
686
+ expect(received).toBe(true);
687
+ }, 1000);
688
+ } else if (data.id === testData2.id) {
689
+ throw new Error('Received data after unsubscribe');
690
+ }
691
+ });
264
692
 
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);
693
+ // Put first piece of data
694
+ await holoSphere.put(testHolon, testLens, testData1);
271
695
  }, 10000);
272
696
 
273
- test('should unsubscribe properly', async () => {
274
- const changes = [];
275
- let subscription;
697
+ test('should handle multiple subscriptions', async () => {
698
+ const testData = { id: 'test1', data: 'test data' };
699
+ const expectedData = {
700
+ ...testData,
701
+ owner: testCredentials.spacename,
702
+ federation: expect.objectContaining({
703
+ origin: testCredentials.spacename,
704
+ timestamp: expect.any(Number)
705
+ })
706
+ };
707
+ let received1 = false;
708
+ let received2 = false;
709
+
710
+ return new Promise((resolve, reject) => {
711
+ const timeout = setTimeout(() => {
712
+ reject(new Error('Subscription timeout'));
713
+ }, 20000);
714
+
715
+ function checkDone() {
716
+ if (received1 && received2) {
717
+ clearTimeout(timeout);
718
+ resolve();
719
+ }
720
+ }
721
+
722
+ holoSphere.subscribe(testHolon, testLens, (data) => {
723
+ if (data.id === testData.id) {
724
+ try {
725
+ received1 = true;
726
+ expect(data).toEqual(expect.objectContaining(expectedData));
727
+ checkDone();
728
+ } catch (error) {
729
+ clearTimeout(timeout);
730
+ reject(error);
731
+ }
732
+ }
733
+ });
276
734
 
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();
735
+ holoSphere.subscribe(testHolon, testLens, (data) => {
736
+ if (data.id === testData.id) {
737
+ try {
738
+ received2 = true;
739
+ expect(data).toEqual(expect.objectContaining(expectedData));
740
+ checkDone();
741
+ } catch (error) {
742
+ clearTimeout(timeout);
743
+ reject(error);
744
+ }
745
+ }
282
746
  });
747
+
748
+ // Put data after both subscriptions
749
+ setTimeout(async () => {
750
+ try {
751
+ await holoSphere.put(testHolon, testLens, testData);
752
+ } catch (error) {
753
+ clearTimeout(timeout);
754
+ reject(error);
755
+ }
756
+ }, 1000);
283
757
  });
758
+ }, 30000);
284
759
 
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);
760
+ afterEach(async () => {
761
+ await holoSphere.deleteAll(testHolon, testLens);
762
+ });
317
763
  });
318
764
 
319
765
  describe('Parse Operations', () => {
@@ -350,6 +796,84 @@ describe('HoloSphere', () => {
350
796
  expect(Array.isArray(results)).toBe(true);
351
797
  expect(results.length).toBeGreaterThan(0);
352
798
  });
799
+
800
+ test('should maintain content integrity in global storage', async () => {
801
+ const testTable = 'testGlobalTable';
802
+
803
+ // Create test data with unique IDs and content
804
+ const testData = [
805
+ { id: 'global1', value: 'value1' },
806
+ { id: 'global2', value: 'value2' },
807
+ { id: 'global3', value: 'value3' }
808
+ ];
809
+
810
+ // Store all test data
811
+ for (const data of testData) {
812
+ await holoSphere.putGlobal(testTable, data);
813
+ }
814
+
815
+ // Retrieve all data
816
+ const retrievedData = await holoSphere.getAllGlobal(testTable);
817
+
818
+ // Sort both arrays by id for comparison
819
+ const sortedTestData = [...testData].sort((a, b) => a.id.localeCompare(b.id));
820
+ const sortedRetrievedData = [...retrievedData].sort((a, b) => a.id.localeCompare(b.id));
821
+
822
+ // Verify no duplicates
823
+ const uniqueIds = new Set(retrievedData.map(item => item.id));
824
+ expect(uniqueIds.size).toBe(testData.length);
825
+
826
+ // Verify all items are present and correct
827
+ expect(sortedRetrievedData).toEqual(sortedTestData);
828
+
829
+ // Verify individual item retrieval
830
+ for (const data of testData) {
831
+ const item = await holoSphere.getGlobal(testTable, data.id);
832
+ expect(item).toEqual(data);
833
+ }
834
+
835
+ // Clean up test data
836
+ await holoSphere.deleteAllGlobal(testTable);
837
+ });
838
+
839
+ test('should handle concurrent global operations without data corruption', async () => {
840
+ const testTable = 'concurrentGlobalTest';
841
+ const numOperations = 5;
842
+ const promises = [];
843
+ const expectedData = [];
844
+
845
+ // Create and store data concurrently with small delays
846
+ for (let i = 0; i < numOperations; i++) {
847
+ const data = { id: `concurrent${i}`, value: `value${i}` };
848
+ expectedData.push(data);
849
+ // Add small delay between operations
850
+ await new Promise(resolve => setTimeout(resolve, 50));
851
+ promises.push(holoSphere.putGlobal(testTable, data));
852
+ }
853
+
854
+ // Wait for all operations to complete
855
+ await Promise.all(promises);
856
+
857
+ // Add delay before verification
858
+ await new Promise(resolve => setTimeout(resolve, 500));
859
+
860
+ // Retrieve and verify data
861
+ const retrievedData = await holoSphere.getAllGlobal(testTable);
862
+
863
+ // Sort both arrays by id for comparison
864
+ const sortedExpectedData = [...expectedData].sort((a, b) => a.id.localeCompare(b.id));
865
+ const sortedRetrievedData = [...retrievedData].sort((a, b) => a.id.localeCompare(b.id));
866
+
867
+ // Verify no duplicates
868
+ const uniqueIds = new Set(retrievedData.map(item => item.id));
869
+ expect(uniqueIds.size).toBe(numOperations);
870
+
871
+ // Verify all items are present and correct
872
+ expect(sortedRetrievedData).toEqual(sortedExpectedData);
873
+
874
+ // Clean up test data
875
+ await holoSphere.deleteAllGlobal(testTable);
876
+ }, 15000); // Increase timeout to 15 seconds
353
877
  });
354
878
 
355
879
  describe('Compute Operations', () => {
@@ -363,34 +887,55 @@ describe('HoloSphere', () => {
363
887
  properties: {
364
888
  id: { type: 'string' },
365
889
  content: { type: 'string' },
366
- data: { type: 'string' }
890
+ timestamp: { type: 'number' }
367
891
  },
368
- required: ['id', 'data']
892
+ required: ['id', 'content']
369
893
  };
370
894
  await holoSphere.setSchema(testLens, schema);
371
895
  });
372
896
 
373
- test('should validate compute parameters', async () => {
374
- await expect(holoSphere.compute(null, null))
897
+ test('should validate required parameters', async () => {
898
+ await expect(holoSphere.compute(null, null, null))
899
+ .rejects.toThrow('compute: Missing required parameters');
900
+
901
+ await expect(holoSphere.compute(testHolon, null, null))
902
+ .rejects.toThrow('compute: Missing required parameters');
903
+
904
+ await expect(holoSphere.compute(testHolon, testLens, null))
375
905
  .rejects.toThrow('compute: Missing required parameters');
376
906
  });
377
907
 
908
+
378
909
  test('should validate holon resolution', async () => {
379
- const invalidHolon = 'invalid';
380
- await expect(holoSphere.compute(invalidHolon, 'testLens'))
381
- .rejects.toThrow('compute: Invalid holon resolution');
910
+ const invalidHolon = h3.latLngToCell(40.7128, -74.0060, 0); // Resolution 0
911
+ await expect(holoSphere.compute(invalidHolon, testLens, 'summarize'))
912
+ .rejects.toThrow('compute: Invalid holon resolution (must be between 1 and 15)');
382
913
  });
383
914
 
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);
915
+ test('should validate depth parameters', async () => {
916
+ await expect(holoSphere.compute(testHolon, testLens, 'summarize', -1))
917
+ .rejects.toThrow('compute: Invalid depth parameter');
918
+
919
+ await expect(holoSphere.compute(testHolon, testLens, 'summarize', 0, 0))
920
+ .rejects.toThrow('compute: Invalid maxDepth parameter (must be between 1 and 15)');
921
+
922
+ await expect(holoSphere.compute(testHolon, testLens, 'summarize', 0, 16))
923
+ .rejects.toThrow('compute: Invalid maxDepth parameter (must be between 1 and 15)');
924
+ });
925
+
926
+ test('should validate operation type', async () => {
927
+ await expect(holoSphere.compute(testHolon, testLens, 'invalid-operation'))
928
+ .rejects.toThrow('compute: Invalid operation (must be "summarize")');
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,10 +963,12 @@ 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);
970
+ // Add small delay between operations to prevent race conditions
971
+ await new Promise(resolve => setTimeout(resolve, 50));
427
972
  promises.push(holoSphere.put(testHolon, testLens, {
428
973
  id: id,
429
974
  data: 'test'
@@ -432,7 +977,9 @@ describe('HoloSphere', () => {
432
977
 
433
978
  // Wait for all operations to complete
434
979
  await Promise.all(promises);
435
- await new Promise(resolve => setTimeout(resolve, 1000));
980
+
981
+ // Add delay before verification to ensure data is settled
982
+ await new Promise(resolve => setTimeout(resolve, 500));
436
983
 
437
984
  // Get and verify results
438
985
  const results = await holoSphere.getAll(testHolon, testLens);
@@ -445,7 +992,7 @@ describe('HoloSphere', () => {
445
992
  expectedIds.forEach(id => {
446
993
  expect(resultIds.has(id)).toBe(true);
447
994
  });
448
- }, 20000);
995
+ }, 15000); // Increase timeout to 15 seconds
449
996
 
450
997
  test('should handle large data sets', async () => {
451
998
  const largeData = {
@@ -456,9 +1003,6 @@ describe('HoloSphere', () => {
456
1003
  // Put the data
457
1004
  await holoSphere.put(testHolon, testLens, largeData);
458
1005
 
459
- // Wait for data to be stored
460
- await new Promise(resolve => setTimeout(resolve, 1000));
461
-
462
1006
  // Get the data back
463
1007
  const result = await holoSphere.get(testHolon, testLens, 'large');
464
1008
 
@@ -466,12 +1010,11 @@ describe('HoloSphere', () => {
466
1010
  expect(result).toBeDefined();
467
1011
  expect(result.id).toBe(largeData.id);
468
1012
  expect(result.data).toBe(largeData.data);
469
- }, 20000);
1013
+ });
470
1014
 
471
1015
  afterEach(async () => {
472
1016
  // Clean up after each test
473
1017
  await holoSphere.deleteAll(testHolon, testLens);
474
- await new Promise(resolve => setTimeout(resolve, 1000));
475
1018
  });
476
1019
  });
477
1020
 
@@ -492,11 +1035,21 @@ describe('HoloSphere', () => {
492
1035
 
493
1036
  afterAll(async () => {
494
1037
  // 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));
1038
+ const testLens = 'testLens';
1039
+ const testHolon = h3.latLngToCell(40.7128, -74.0060, 7);
1040
+ await holoSphere.deleteAll(testHolon, testLens);
1041
+
1042
+ // Clean up test tables
1043
+ await holoSphere.deleteAllGlobal('testTable');
1044
+ await holoSphere.deleteAllGlobal('testGlobalTable');
1045
+ await holoSphere.deleteAllGlobal('concurrentGlobalTest');
1046
+
1047
+ // Clean up test space
1048
+ await holoSphere.deleteGlobal('spaces', testCredentials.spacename);
1049
+
1050
+ // Logout
1051
+ if (holoSphere.currentSpace) {
1052
+ await holoSphere.logout();
1053
+ }
501
1054
  });
502
1055
  });