holosphere 1.1.1 → 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,109 +64,241 @@ describe('HoloSphere', () => {
34
64
  required: ['id', 'data']
35
65
  };
36
66
 
37
- test('should set and get schema', async () => {
38
- // Set the schema
39
- const setResult = await holoSphere.setSchema(testLens, validSchema);
40
- expect(setResult).toBe(true);
41
-
42
- // Wait for GunDB to process
43
- await new Promise(resolve => setTimeout(resolve, 100));
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
+ });
44
73
 
45
- // Get and verify the schema
74
+ test('should set and get schema', async () => {
75
+ await holoSphere.setSchema(testLens, validSchema);
46
76
  const retrievedSchema = await holoSphere.getSchema(testLens);
47
77
  expect(retrievedSchema).toBeDefined();
48
78
  expect(retrievedSchema).toEqual(validSchema);
49
- }, 5000);
79
+ });
50
80
 
51
81
  test('should handle invalid schema parameters', async () => {
52
- const nullResult = await holoSphere.setSchema(null, null);
53
- expect(nullResult).toBe(false);
54
-
55
- const missingLensResult = await holoSphere.setSchema(undefined, validSchema);
56
- expect(missingLensResult).toBe(false);
57
-
58
- const missingSchemaResult = await holoSphere.setSchema(testLens, null);
59
- expect(missingSchemaResult).toBe(false);
82
+ await expect(holoSphere.setSchema(null, null))
83
+ .rejects.toThrow('setSchema: Missing required parameters');
60
84
  });
61
85
 
62
86
  test('should enforce strict mode schema validation', async () => {
63
87
  const strictHoloSphere = new HoloSphere(testAppName, true);
64
88
 
65
- // Test cases for invalid schemas
66
- const invalidSchemas = [
67
- {
68
- // Missing type field
69
- properties: {
70
- id: { type: 'string' }
71
- }
72
- },
73
- {
74
- // Missing properties
75
- type: 'object'
76
- },
89
+ // Login to the strict instance
90
+ await strictHoloSphere.login(testCredentials.spacename, testCredentials.password);
91
+
92
+ const invalidSchema = {
93
+ type: 'object',
94
+ properties: {
95
+ id: { type: 'string' }
96
+ }
97
+ };
98
+
99
+ await expect(strictHoloSphere.setSchema(testLens, invalidSchema))
100
+ .rejects.toThrow();
101
+
102
+ // Clean up
103
+ await strictHoloSphere.logout();
104
+ });
105
+
106
+ test('should handle schema retrieval for non-existent lens', async () => {
107
+ const result = await holoSphere.getSchema('nonexistent-lens');
108
+ expect(result).toBeNull();
109
+ });
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 = [
77
120
  {
78
- // Missing required fields
79
121
  type: 'object',
80
122
  properties: {
81
- id: { type: 'string' }
82
- }
123
+ id: { type: 'string' },
124
+ data: { type: 'string' }
125
+ },
126
+ required: ['id', 'data']
83
127
  },
84
128
  {
85
- // Invalid property type
86
129
  type: 'object',
87
130
  properties: {
88
- id: { type: 123 } // Should be string
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
+ }
89
143
  },
90
- required: ['id']
144
+ required: ['id', 'data']
91
145
  }
92
146
  ];
93
147
 
94
- // Test each invalid schema
95
- for (const invalidSchema of invalidSchemas) {
96
- const setResult = await strictHoloSphere.setSchema(testLens, invalidSchema);
97
- expect(setResult).toBe(false);
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);
98
238
  }
239
+ }, 10000); // Increase timeout to 10 seconds
99
240
 
100
- // Valid schema should work in strict mode
101
- const validSchema = {
241
+ test('should handle schema updates correctly', async () => {
242
+ const testLens = 'schemaUpdateTest';
243
+
244
+ // Initial schema
245
+ const initialSchema = {
102
246
  type: 'object',
103
247
  properties: {
104
248
  id: { type: 'string' },
105
249
  data: { type: 'string' }
106
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
+ },
107
262
  required: ['id', 'data']
108
263
  };
109
264
 
110
- const validResult = await strictHoloSphere.setSchema(testLens, validSchema);
111
- expect(validResult).toBe(true);
265
+ // Set initial schema
266
+ await holoSphere.setSchema(testLens, initialSchema);
112
267
 
113
- // Verify schema was stored correctly
114
- const retrievedSchema = await strictHoloSphere.getSchema(testLens);
115
- expect(retrievedSchema).toEqual(validSchema);
116
- }, 5000);
268
+ // Verify initial schema
269
+ let retrievedSchema = await holoSphere.getSchema(testLens);
270
+ expect(retrievedSchema).toEqual(initialSchema);
117
271
 
118
- test('should handle schema retrieval for non-existent lens', async () => {
119
- const result = await holoSphere.getSchema('nonexistent-lens');
120
- expect(result).toBeNull();
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);
121
278
  });
122
279
 
123
280
  afterEach(async () => {
124
281
  // Clean up schemas after each test
125
282
  await holoSphere.gun.get(holoSphere.appname)
126
- .get(testLens)
127
- .get('schema')
128
- .put(null);
129
-
130
- // Wait for GunDB to process
131
- await new Promise(resolve => setTimeout(resolve, 100));
283
+ .get(testLens)
284
+ .get('schema')
285
+ .put(null);
132
286
  });
133
287
  });
134
288
 
135
289
  describe('Data Operations', () => {
136
290
  const testHolon = h3.latLngToCell(40.7128, -74.0060, 7);
137
291
  const testLens = 'testLens';
138
- const validData = { id: 'test123', data: 'test data' };
292
+ const validData = { id: 'test1', data: 'test data' };
139
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
+ };
140
302
 
141
303
  beforeEach(async () => {
142
304
  // Set up schema for validation tests
@@ -144,7 +306,8 @@ describe('HoloSphere', () => {
144
306
  type: 'object',
145
307
  properties: {
146
308
  id: { type: 'string' },
147
- data: { type: 'string' }
309
+ data: { type: 'string' },
310
+ owner: { type: 'string' }
148
311
  },
149
312
  required: ['id', 'data']
150
313
  };
@@ -153,16 +316,21 @@ describe('HoloSphere', () => {
153
316
 
154
317
  test('should put and get data with schema validation', async () => {
155
318
  // Test valid data
156
- const putResult = await holoSphere.put(testHolon, testLens, validData);
157
- expect(putResult).toBe(true);
319
+ await expect(holoSphere.put(testHolon, testLens, validData))
320
+ .resolves.toBe(true);
321
+
322
+ // Test invalid data
323
+ await expect(holoSphere.put(testHolon, testLens, invalidData))
324
+ .rejects.toThrow('Schema validation failed');
158
325
 
159
- const getResult = await holoSphere.get(testHolon, testLens, validData.id);
160
- expect(getResult).toEqual(validData);
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));
161
329
 
162
- // Test invalid data
163
- const invalidPutResult = await holoSphere.put(testHolon, testLens, invalidData);
164
- expect(invalidPutResult).toBe(false);
165
- }, 10000);
330
+ // Verify the invalid data was not stored
331
+ const invalidResult = await holoSphere.get(testHolon, testLens, invalidData.id);
332
+ expect(invalidResult).toBeNull();
333
+ });
166
334
 
167
335
  test('should get all data with schema validation', async () => {
168
336
  await holoSphere.put(testHolon, testLens, validData);
@@ -172,7 +340,7 @@ describe('HoloSphere', () => {
172
340
  expect(Array.isArray(results)).toBeTruthy();
173
341
  expect(results.length).toBeGreaterThan(0);
174
342
  expect(results.some(item => item.id === validData.id)).toBeTruthy();
175
- }, 10000);
343
+ });
176
344
 
177
345
  test('should delete data', async () => {
178
346
  await holoSphere.put(testHolon, testLens, validData);
@@ -180,7 +348,7 @@ describe('HoloSphere', () => {
180
348
 
181
349
  const result = await holoSphere.get(testHolon, testLens, validData.id);
182
350
  expect(result).toBeNull();
183
- }, 10000);
351
+ });
184
352
 
185
353
  test('should delete all data', async () => {
186
354
  await holoSphere.put(testHolon, testLens, validData);
@@ -191,11 +359,14 @@ describe('HoloSphere', () => {
191
359
 
192
360
  const results = await holoSphere.getAll(testHolon, testLens);
193
361
  expect(results).toEqual([]);
194
- }, 10000);
362
+ });
195
363
 
196
364
  test('should enforce strict mode data validation', async () => {
197
365
  const strictHoloSphere = new HoloSphere(testAppName, true);
198
366
 
367
+ // Login to the strict instance
368
+ await strictHoloSphere.login(testCredentials.spacename, testCredentials.password);
369
+
199
370
  // Define schema for strict mode tests
200
371
  const strictSchema = {
201
372
  type: 'object',
@@ -210,18 +381,174 @@ describe('HoloSphere', () => {
210
381
  await strictHoloSphere.setSchema(testLens, strictSchema);
211
382
 
212
383
  // Try to put data without schema in strict mode
213
- const noSchemaResult = await strictHoloSphere.put(testHolon, 'no-schema-lens', validData);
214
- expect(noSchemaResult).toBe(false);
215
-
216
- // Try to get data without schema in strict mode
217
- const noSchemaData = await strictHoloSphere.getAll(testHolon, 'no-schema-lens');
218
- expect(noSchemaData).toEqual([]);
219
-
220
- // Invalid data should be removed in strict mode
221
- await strictHoloSphere.put(testHolon, testLens, invalidData);
222
- const results = await strictHoloSphere.getAll(testHolon, testLens);
223
- expect(results.some(item => item.id === invalidData.id)).toBe(false);
224
- }, 10000);
384
+ await expect(strictHoloSphere.put(testHolon, 'no-schema-lens', validData))
385
+ .rejects.toThrow('Schema required in strict mode');
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
+ });
225
552
  });
226
553
 
227
554
  describe('Node Operations', () => {
@@ -231,49 +558,37 @@ describe('HoloSphere', () => {
231
558
 
232
559
  test('should put and get node', async () => {
233
560
  await holoSphere.putNode(testHolon, testLens, testNode);
234
-
235
- // Wait for GunDB to process
236
- await new Promise(resolve => setTimeout(resolve, 100));
237
-
561
+
238
562
  const result = await holoSphere.getNode(testHolon, testLens, 'value');
239
563
  expect(result).toBeDefined();
240
564
  expect(result).toBe('test node data');
241
- }, 10000);
565
+ });
242
566
 
243
567
  test('should delete node', async () => {
244
568
  // First put the node
245
569
  await holoSphere.putNode(testHolon, testLens, testNode);
246
- await new Promise(resolve => setTimeout(resolve, 100));
247
-
570
+
248
571
  // Verify node exists
249
572
  const beforeDelete = await holoSphere.getNode(testHolon, testLens, 'value');
250
573
  expect(beforeDelete).toBe('test node data');
251
-
574
+
252
575
  // Delete the node
253
576
  const deleteResult = await holoSphere.deleteNode(testHolon, testLens, 'value');
254
577
  expect(deleteResult).toBe(true);
255
-
256
- // Wait for deletion to process
257
- await new Promise(resolve => setTimeout(resolve, 100));
258
-
578
+
259
579
  // Verify node is deleted
260
580
  const afterDelete = await holoSphere.getNode(testHolon, testLens, 'value');
261
581
  expect(afterDelete).toBeNull();
262
- }, 10000);
582
+ });
263
583
 
264
584
  test('should handle invalid node operations', async () => {
265
- // Test missing parameters
266
- const nullResult = await holoSphere.deleteNode(null, null, null);
267
- expect(nullResult).toBe(false);
268
-
269
- const nullGet = await holoSphere.getNode(null, null, null);
270
- expect(nullGet).toBeNull();
585
+ await expect(holoSphere.deleteNode(null, null, null))
586
+ .rejects.toThrow('deleteNode: Missing required parameters');
271
587
  });
272
588
 
273
589
  afterEach(async () => {
274
590
  // Clean up after each test
275
- await holoSphere.deleteNode(testHolon, testLens, 'value');
276
- await new Promise(resolve => setTimeout(resolve, 100));
591
+ await holoSphere.deleteNode(testHolon, testLens, 'value');
277
592
  });
278
593
  });
279
594
 
@@ -302,13 +617,439 @@ describe('HoloSphere', () => {
302
617
  });
303
618
  });
304
619
 
620
+ describe('Subscription Operations', () => {
621
+ const testHolon = h3.latLngToCell(40.7128, -74.0060, 7);
622
+ const testLens = 'testLens';
623
+
624
+ beforeEach(async () => {
625
+ await holoSphere.deleteAll(testHolon, testLens);
626
+ });
627
+
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
+ });
658
+
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
+ });
692
+
693
+ // Put first piece of data
694
+ await holoSphere.put(testHolon, testLens, testData1);
695
+ }, 10000);
696
+
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
+ });
734
+
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
+ }
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);
757
+ });
758
+ }, 30000);
759
+
760
+ afterEach(async () => {
761
+ await holoSphere.deleteAll(testHolon, testLens);
762
+ });
763
+ });
764
+
765
+ describe('Parse Operations', () => {
766
+ test('should handle null input', async () => {
767
+ await expect(holoSphere.parse(null))
768
+ .rejects.toThrow('parse: No data provided');
769
+ });
770
+
771
+ test('should parse valid JSON string', async () => {
772
+ const result = await holoSphere.parse('{"test": "data"}');
773
+ expect(result).toEqual({ test: 'data' });
774
+ });
775
+ });
776
+
777
+ describe('Global Operations', () => {
778
+ test('should put and get global data', async () => {
779
+ const testData = { id: 'global1', value: 'test' };
780
+ await holoSphere.putGlobal('testTable', testData);
781
+
782
+ const result = await holoSphere.getGlobal('testTable', 'global1');
783
+ expect(result).toEqual(testData);
784
+ });
785
+
786
+ test('should handle missing parameters in global operations', async () => {
787
+ await expect(holoSphere.putGlobal(null, null))
788
+ .rejects.toThrow('Table name and data are required');
789
+ });
790
+
791
+ test('should handle getAllGlobal', async () => {
792
+ await holoSphere.putGlobal('testTable', { id: 'g1', value: 'test1' });
793
+ await holoSphere.putGlobal('testTable', { id: 'g2', value: 'test2' });
794
+
795
+ const results = await holoSphere.getAllGlobal('testTable');
796
+ expect(Array.isArray(results)).toBe(true);
797
+ expect(results.length).toBeGreaterThan(0);
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
877
+ });
878
+
879
+ describe('Compute Operations', () => {
880
+ const testHolon = h3.latLngToCell(40.7128, -74.0060, 7);
881
+ const testLens = 'testLens';
882
+
883
+ beforeEach(async () => {
884
+ // Set up schema for compute tests
885
+ const schema = {
886
+ type: 'object',
887
+ properties: {
888
+ id: { type: 'string' },
889
+ content: { type: 'string' },
890
+ timestamp: { type: 'number' }
891
+ },
892
+ required: ['id', 'content']
893
+ };
894
+ await holoSphere.setSchema(testLens, schema);
895
+ });
896
+
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))
905
+ .rejects.toThrow('compute: Missing required parameters');
906
+ });
907
+
908
+
909
+ test('should validate holon resolution', async () => {
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)');
913
+ });
914
+
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
+ });
939
+ });
940
+
941
+ describe('Error Handling', () => {
942
+ const testHolon = h3.latLngToCell(40.7128, -74.0060, 7);
943
+ const testLens = 'testLens';
944
+
945
+ beforeEach(async () => {
946
+ // Clear all existing data
947
+ await holoSphere.deleteAll(testHolon, testLens);
948
+
949
+ // Set up fresh schema
950
+ const schema = {
951
+ type: 'object',
952
+ properties: {
953
+ id: { type: 'string' },
954
+ data: { type: 'string' }
955
+ },
956
+ required: ['id', 'data']
957
+ };
958
+ await holoSphere.setSchema(testLens, schema);
959
+ });
960
+
961
+ test('should handle concurrent operations', async () => {
962
+ const numOperations = 10;
963
+ const promises = [];
964
+ const expectedIds = new Set();
965
+
966
+ // Create concurrent put operations with small delays between them
967
+ for (let i = 0; i < numOperations; i++) {
968
+ const id = `concurrent${i}`;
969
+ expectedIds.add(id);
970
+ // Add small delay between operations to prevent race conditions
971
+ await new Promise(resolve => setTimeout(resolve, 50));
972
+ promises.push(holoSphere.put(testHolon, testLens, {
973
+ id: id,
974
+ data: 'test'
975
+ }));
976
+ }
977
+
978
+ // Wait for all operations to complete
979
+ await Promise.all(promises);
980
+
981
+ // Add delay before verification to ensure data is settled
982
+ await new Promise(resolve => setTimeout(resolve, 500));
983
+
984
+ // Get and verify results
985
+ const results = await holoSphere.getAll(testHolon, testLens);
986
+ const resultIds = new Set(results.map(r => r.id));
987
+
988
+ // Verify we have exactly the expected number of unique results
989
+ expect(resultIds.size).toBe(numOperations);
990
+
991
+ // Verify all expected IDs are present
992
+ expectedIds.forEach(id => {
993
+ expect(resultIds.has(id)).toBe(true);
994
+ });
995
+ }, 15000); // Increase timeout to 15 seconds
996
+
997
+ test('should handle large data sets', async () => {
998
+ const largeData = {
999
+ id: 'large',
1000
+ data: 'x'.repeat(1000000)
1001
+ };
1002
+
1003
+ // Put the data
1004
+ await holoSphere.put(testHolon, testLens, largeData);
1005
+
1006
+ // Get the data back
1007
+ const result = await holoSphere.get(testHolon, testLens, 'large');
1008
+
1009
+ // Verify the data
1010
+ expect(result).toBeDefined();
1011
+ expect(result.id).toBe(largeData.id);
1012
+ expect(result.data).toBe(largeData.data);
1013
+ });
1014
+
1015
+ afterEach(async () => {
1016
+ // Clean up after each test
1017
+ await holoSphere.deleteAll(testHolon, testLens);
1018
+ });
1019
+ });
1020
+
1021
+ describe('OpenAI Integration', () => {
1022
+ test('should handle missing OpenAI key', async () => {
1023
+ const noAIHoloSphere = new HoloSphere('test');
1024
+ const result = await noAIHoloSphere.summarize('test content');
1025
+ expect(result).toBe('OpenAI not initialized, please specify the API key in the constructor.');
1026
+ });
1027
+
1028
+ test.skip('should summarize content with valid OpenAI key', async () => {
1029
+ const hsWithAI = new HoloSphere('test', false, process.env.OPENAI_API_KEY);
1030
+ const summary = await hsWithAI.summarize('Test content to summarize');
1031
+ expect(typeof summary).toBe('string');
1032
+ expect(summary.length).toBeGreaterThan(0);
1033
+ });
1034
+ });
1035
+
305
1036
  afterAll(async () => {
306
1037
  // Clean up test data
307
1038
  const testLens = 'testLens';
308
1039
  const testHolon = h3.latLngToCell(40.7128, -74.0060, 7);
309
1040
  await holoSphere.deleteAll(testHolon, testLens);
310
1041
 
311
- // Allow time for Gun to process
312
- await new Promise(resolve => setTimeout(resolve, 1000));
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
+ }
313
1054
  });
314
1055
  });