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.
- package/holosphere.js +1143 -281
- package/package.json +3 -2
- package/test/ai.test.js +233 -0
- package/test/federation.test.js +363 -0
- package/test/holosphere.test.js +681 -133
- package/test/spacesauth.test.js +335 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "holosphere",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Holonic Geospatial Communication Infrastructure",
|
|
5
5
|
"main": "holosphere.js",
|
|
6
6
|
"types": "holosphere.d.ts",
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
"license": "GPL-3.0-or-later",
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"ajv": "^8.12.0",
|
|
17
|
+
"dotenv": "^16.4.7",
|
|
17
18
|
"gun": "^0.2020.1240",
|
|
18
19
|
"h3-js": "^4.1.0",
|
|
19
|
-
"openai": "^4.
|
|
20
|
+
"openai": "^4.85.1"
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
22
23
|
"jest": "^29.7.0",
|
package/test/ai.test.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import HoloSphere from '../holosphere.js';
|
|
2
|
+
import * as h3 from 'h3-js';
|
|
3
|
+
import { jest } from '@jest/globals';
|
|
4
|
+
import 'dotenv/config';
|
|
5
|
+
|
|
6
|
+
// Set global timeout for all tests
|
|
7
|
+
jest.setTimeout(120000);
|
|
8
|
+
|
|
9
|
+
describe('AI Operations', () => {
|
|
10
|
+
let holoSphere;
|
|
11
|
+
const testAppName = 'test-ai-app';
|
|
12
|
+
const testHolon = h3.latLngToCell(40.7128, -74.0060, 7);
|
|
13
|
+
const testLens = 'aiTestLens';
|
|
14
|
+
const testCredentials = {
|
|
15
|
+
spacename: 'aitest@example.com',
|
|
16
|
+
password: 'AiTest123!'
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
beforeAll(async () => {
|
|
20
|
+
holoSphere = new HoloSphere(testAppName, false, process.env.OPENAI_API_KEY);
|
|
21
|
+
|
|
22
|
+
// Clean up any existing test space and data
|
|
23
|
+
try {
|
|
24
|
+
await holoSphere.deleteAllGlobal('federation');
|
|
25
|
+
await holoSphere.deleteGlobal('spaces', testCredentials.spacename);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.log('Cleanup error (can be ignored):', error);
|
|
28
|
+
}
|
|
29
|
+
// Create and login to test space
|
|
30
|
+
await holoSphere.createSpace(testCredentials.spacename, testCredentials.password);
|
|
31
|
+
await holoSphere.login(testCredentials.spacename, testCredentials.password);
|
|
32
|
+
// Set up base schema for compute tests
|
|
33
|
+
const baseSchema = {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
id: { type: 'string' },
|
|
37
|
+
content: { type: 'string' },
|
|
38
|
+
value: { type: 'number' },
|
|
39
|
+
tags: { type: 'array', items: { type: 'string' } },
|
|
40
|
+
timestamp: { type: 'number' }
|
|
41
|
+
},
|
|
42
|
+
required: ['id']
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
await holoSphere.setSchema(testLens, baseSchema);
|
|
46
|
+
}, 30000);
|
|
47
|
+
|
|
48
|
+
describe('Summarize Operations', () => {
|
|
49
|
+
test('should generate summary from text content', async () => {
|
|
50
|
+
const testContent = `
|
|
51
|
+
The HoloSphere project is a decentralized data management system.
|
|
52
|
+
It uses Gun.js for peer-to-peer data synchronization and SEA for encryption.
|
|
53
|
+
The system supports federation between spaces and implements schema validation.
|
|
54
|
+
Data can be organized in holons and viewed through different lenses.
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
const summary = await holoSphere.summarize(testContent);
|
|
58
|
+
console.log("summary",summary);
|
|
59
|
+
expect(summary).toBeDefined();
|
|
60
|
+
expect(typeof summary).toBe('string');
|
|
61
|
+
expect(summary.length).toBeGreaterThan(0);
|
|
62
|
+
}, 15000);
|
|
63
|
+
|
|
64
|
+
test('should handle empty content gracefully', async () => {
|
|
65
|
+
const summary = await holoSphere.summarize('');
|
|
66
|
+
expect(summary).toBeDefined();
|
|
67
|
+
expect(typeof summary).toBe('string');
|
|
68
|
+
}, 10000);
|
|
69
|
+
|
|
70
|
+
test('should handle long content', async () => {
|
|
71
|
+
const longContent = Array(10).fill(
|
|
72
|
+
'This is a long paragraph of text that needs to be summarized. ' +
|
|
73
|
+
'It contains multiple sentences with various information. ' +
|
|
74
|
+
'The summary should capture the key points while remaining concise.'
|
|
75
|
+
).join('\n');
|
|
76
|
+
|
|
77
|
+
const summary = await holoSphere.summarize(longContent);
|
|
78
|
+
expect(summary).toBeDefined();
|
|
79
|
+
expect(typeof summary).toBe('string');
|
|
80
|
+
expect(summary.length).toBeLessThan(longContent.length);
|
|
81
|
+
}, 20000);
|
|
82
|
+
|
|
83
|
+
test('should fail gracefully without API key', async () => {
|
|
84
|
+
const noKeyHoloSphere = new HoloSphere(testAppName, false);
|
|
85
|
+
const result = await noKeyHoloSphere.summarize('Test content');
|
|
86
|
+
expect(result).toBe('OpenAI not initialized, please specify the API key in the constructor.');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('Compute Operations', () => {
|
|
91
|
+
beforeEach(async () => {
|
|
92
|
+
// Ensure we're logged in
|
|
93
|
+
if (!holoSphere.currentSpace) {
|
|
94
|
+
await holoSphere.login(testCredentials.spacename, testCredentials.password);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Clean up any existing test data
|
|
98
|
+
await holoSphere.deleteAll(testHolon, testLens);
|
|
99
|
+
}, 15000);
|
|
100
|
+
|
|
101
|
+
test('should compute summaries for nested holons', async () => {
|
|
102
|
+
const childHolon = h3.cellToChildren(testHolon, 8)[0];
|
|
103
|
+
const testData = {
|
|
104
|
+
id: 'test1',
|
|
105
|
+
content: 'This is test content for the child holon that should be summarized.',
|
|
106
|
+
timestamp: Date.now()
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Put data in child holon
|
|
110
|
+
await holoSphere.put(childHolon, testLens, testData);
|
|
111
|
+
|
|
112
|
+
// Compute summaries
|
|
113
|
+
const result = await holoSphere.compute(childHolon, testLens, {
|
|
114
|
+
operation: 'summarize',
|
|
115
|
+
fields: ['content'],
|
|
116
|
+
targetField: 'summary'
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(result).toBeDefined();
|
|
120
|
+
expect(result.id).toMatch(/_summarize$/);
|
|
121
|
+
expect(result.summary).toBeDefined();
|
|
122
|
+
expect(typeof result.summary).toBe('string');
|
|
123
|
+
}, 60000);
|
|
124
|
+
|
|
125
|
+
test('should compute aggregations for numeric fields', async () => {
|
|
126
|
+
const childHolon = h3.cellToChildren(testHolon, 8)[0];
|
|
127
|
+
const testData = [
|
|
128
|
+
{ id: 'test1', value: 10, timestamp: Date.now() },
|
|
129
|
+
{ id: 'test2', value: 20, timestamp: Date.now() }
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
// Put test data
|
|
133
|
+
await Promise.all(testData.map(data => holoSphere.put(childHolon, testLens, data)));
|
|
134
|
+
|
|
135
|
+
// Compute aggregation
|
|
136
|
+
const result = await holoSphere.compute(childHolon, testLens, {
|
|
137
|
+
operation: 'aggregate',
|
|
138
|
+
fields: ['value']
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
expect(result).toBeDefined();
|
|
142
|
+
expect(result.id).toMatch(/_aggregate$/);
|
|
143
|
+
expect(result.value).toBe(30);
|
|
144
|
+
}, 30000);
|
|
145
|
+
|
|
146
|
+
test('should compute concatenations for array fields', async () => {
|
|
147
|
+
const childHolon = h3.cellToChildren(testHolon, 8)[0];
|
|
148
|
+
const testData = [
|
|
149
|
+
{ id: 'test1', tags: ['tag1', 'tag2'], timestamp: Date.now() },
|
|
150
|
+
{ id: 'test2', tags: ['tag2', 'tag3'], timestamp: Date.now() }
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
// Put test data
|
|
154
|
+
await Promise.all(testData.map(data => holoSphere.put(childHolon, testLens, data)));
|
|
155
|
+
|
|
156
|
+
// Compute concatenation
|
|
157
|
+
const result = await holoSphere.compute(childHolon, testLens, {
|
|
158
|
+
operation: 'concatenate',
|
|
159
|
+
fields: ['tags']
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(result).toBeDefined();
|
|
163
|
+
expect(result.id).toMatch(/_concatenate$/);
|
|
164
|
+
expect(result.tags).toEqual(['tag1', 'tag2', 'tag3']);
|
|
165
|
+
}, 30000);
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
test('should handle empty holons', async () => {
|
|
169
|
+
// Clean up any existing data first
|
|
170
|
+
await holoSphere.deleteAll(testHolon, testLens);
|
|
171
|
+
|
|
172
|
+
// Try to compute on empty holon
|
|
173
|
+
const result = await holoSphere.compute(testHolon, testLens, {
|
|
174
|
+
operation: 'summarize',
|
|
175
|
+
fields: ['content']
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(result).toBeNull();
|
|
179
|
+
}, 30000);
|
|
180
|
+
|
|
181
|
+
test('should compute hierarchy across multiple levels', async () => {
|
|
182
|
+
const childHolon = h3.cellToChildren(testHolon, 9)[0];
|
|
183
|
+
const testData = {
|
|
184
|
+
id: 'test-hierarchy',
|
|
185
|
+
content: 'Content for testing hierarchy computation',
|
|
186
|
+
value: 42,
|
|
187
|
+
tags: ['test', 'hierarchy'],
|
|
188
|
+
timestamp: Date.now()
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Put test data
|
|
192
|
+
await holoSphere.put(childHolon, testLens, testData);
|
|
193
|
+
|
|
194
|
+
// Compute hierarchy
|
|
195
|
+
const results = await holoSphere.computeHierarchy(childHolon, testLens, {
|
|
196
|
+
operation: 'summarize',
|
|
197
|
+
fields: ['content'],
|
|
198
|
+
targetField: 'summary'
|
|
199
|
+
}, 3);
|
|
200
|
+
|
|
201
|
+
expect(Array.isArray(results)).toBe(true);
|
|
202
|
+
expect(results.length).toBeGreaterThan(0);
|
|
203
|
+
results.forEach(result => {
|
|
204
|
+
expect(result.id).toMatch(/_summarize$/);
|
|
205
|
+
expect(result.summary).toBeDefined();
|
|
206
|
+
expect(typeof result.summary).toBe('string');
|
|
207
|
+
});
|
|
208
|
+
}, 60000);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
afterEach(async () => {
|
|
212
|
+
// Clean up test data
|
|
213
|
+
if (holoSphere.currentSpace) {
|
|
214
|
+
await holoSphere.deleteAll(testHolon, testLens);
|
|
215
|
+
}
|
|
216
|
+
}, 15000);
|
|
217
|
+
|
|
218
|
+
afterAll(async () => {
|
|
219
|
+
// Clean up test space and data
|
|
220
|
+
try {
|
|
221
|
+
await holoSphere.deleteAll(testHolon, testLens);
|
|
222
|
+
await holoSphere.deleteGlobal('spaces', testCredentials.spacename);
|
|
223
|
+
await holoSphere.deleteAllGlobal('federation');
|
|
224
|
+
} catch (error) {
|
|
225
|
+
console.log('Cleanup error (can be ignored):', error);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Clean up Gun instance
|
|
229
|
+
if (holoSphere.gun) {
|
|
230
|
+
holoSphere.gun.off();
|
|
231
|
+
}
|
|
232
|
+
}, 30000);
|
|
233
|
+
});
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import HoloSphere from '../holosphere.js';
|
|
2
|
+
import { jest } from '@jest/globals';
|
|
3
|
+
|
|
4
|
+
// Set global timeout for all tests
|
|
5
|
+
jest.setTimeout(3000);
|
|
6
|
+
|
|
7
|
+
describe('Federation Operations', () => {
|
|
8
|
+
const testAppName = 'test-app';
|
|
9
|
+
const testHolon = 'test-holon';
|
|
10
|
+
const testLens = 'test-lens';
|
|
11
|
+
|
|
12
|
+
// Global HoloSphere instances
|
|
13
|
+
let holoSphere; // Non-strict instance
|
|
14
|
+
let strictHoloSphere; // Strict instance
|
|
15
|
+
let space1, space2;
|
|
16
|
+
|
|
17
|
+
beforeAll(async () => {
|
|
18
|
+
// Create global instances
|
|
19
|
+
holoSphere = new HoloSphere(testAppName, false);
|
|
20
|
+
strictHoloSphere = new HoloSphere(testAppName, true);
|
|
21
|
+
|
|
22
|
+
space1 = { spacename: 'space1', password: 'pass1' };
|
|
23
|
+
space2 = { spacename: 'space2', password: 'pass2' };
|
|
24
|
+
|
|
25
|
+
// Clean up any existing test spaces and federation data
|
|
26
|
+
try {
|
|
27
|
+
await holoSphere.deleteAllGlobal('federation');
|
|
28
|
+
await holoSphere.deleteGlobal('spaces', space1.spacename);
|
|
29
|
+
await holoSphere.deleteGlobal('spaces', space2.spacename);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
// Ignore errors during cleanup
|
|
32
|
+
console.log('Cleanup error (expected):', error.message);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Create fresh test spaces with retries
|
|
36
|
+
for (let i = 0; i < 3; i++) {
|
|
37
|
+
try {
|
|
38
|
+
await holoSphere.createSpace(space1.spacename, space1.password);
|
|
39
|
+
await holoSphere.createSpace(space2.spacename, space2.password);
|
|
40
|
+
break;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.log('Space creation attempt', i + 1, 'failed:', error.message);
|
|
43
|
+
if (i === 2) throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
// Verify spaces were created
|
|
49
|
+
const space1Created = await holoSphere.getGlobal('spaces', space1.spacename);
|
|
50
|
+
const space2Created = await holoSphere.getGlobal('spaces', space2.spacename);
|
|
51
|
+
|
|
52
|
+
if (!space1Created || !space2Created) {
|
|
53
|
+
throw new Error('Failed to create test spaces');
|
|
54
|
+
}
|
|
55
|
+
}, 30000);
|
|
56
|
+
|
|
57
|
+
beforeEach(async () => {
|
|
58
|
+
// Clean up any existing federation data
|
|
59
|
+
await holoSphere.deleteAllGlobal('federation');
|
|
60
|
+
|
|
61
|
+
// Clean up any existing test data
|
|
62
|
+
await holoSphere.deleteAll(testHolon, testLens);
|
|
63
|
+
|
|
64
|
+
// Set up base schema for all tests
|
|
65
|
+
const baseSchema = {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
id: { type: 'string' },
|
|
69
|
+
name: { type: 'string' },
|
|
70
|
+
data: { type: 'string' },
|
|
71
|
+
federation: {
|
|
72
|
+
type: 'object',
|
|
73
|
+
properties: {
|
|
74
|
+
origin: { type: 'string' },
|
|
75
|
+
timestamp: { type: 'number' }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
required: ['id']
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
await holoSphere.setSchema(testLens, baseSchema);
|
|
83
|
+
await strictHoloSphere.setSchema(testLens, baseSchema);
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
// Verify spaces exist before proceeding
|
|
87
|
+
const space1Exists = await holoSphere.getGlobal('spaces', space1.spacename);
|
|
88
|
+
const space2Exists = await holoSphere.getGlobal('spaces', space2.spacename);
|
|
89
|
+
|
|
90
|
+
// If spaces don't exist, recreate them
|
|
91
|
+
if (!space1Exists) {
|
|
92
|
+
try {
|
|
93
|
+
await holoSphere.createSpace(space1.spacename, space1.password);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (error.message !== 'Space already exists') {
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (!space2Exists) {
|
|
101
|
+
try {
|
|
102
|
+
await holoSphere.createSpace(space2.spacename, space2.password);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (error.message !== 'Space already exists') {
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Verify spaces again
|
|
111
|
+
const space1Verified = await holoSphere.getGlobal('spaces', space1.spacename);
|
|
112
|
+
const space2Verified = await holoSphere.getGlobal('spaces', space2.spacename);
|
|
113
|
+
|
|
114
|
+
if (!space1Verified || !space2Verified) {
|
|
115
|
+
throw new Error('Test spaces not found - test environment not properly set up');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Ensure both instances are logged out
|
|
119
|
+
if (holoSphere.currentSpace) {
|
|
120
|
+
await holoSphere.logout();
|
|
121
|
+
}
|
|
122
|
+
if (strictHoloSphere.currentSpace) {
|
|
123
|
+
await strictHoloSphere.logout();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Login as first space to holoSphere
|
|
127
|
+
await holoSphere.login(space1.spacename, space1.password);
|
|
128
|
+
|
|
129
|
+
}, 20000);
|
|
130
|
+
|
|
131
|
+
test('should create federation relationship between spaces', async () => {
|
|
132
|
+
// Create federation relationship
|
|
133
|
+
await holoSphere.federate(space1.spacename, space2.spacename);
|
|
134
|
+
|
|
135
|
+
// Verify federation was created
|
|
136
|
+
const fedInfo = await holoSphere.getFederation(space1.spacename);
|
|
137
|
+
expect(fedInfo).toBeDefined();
|
|
138
|
+
expect(fedInfo.federation).toContain(space2.spacename);
|
|
139
|
+
}, 10000);
|
|
140
|
+
|
|
141
|
+
test('should establish bidirectional federation', async () => {
|
|
142
|
+
// Create bidirectional federation
|
|
143
|
+
await holoSphere.federate(space1.spacename, space2.spacename);
|
|
144
|
+
|
|
145
|
+
// Login to space1 to verify federation
|
|
146
|
+
await holoSphere.login(space1.spacename, space1.password);
|
|
147
|
+
|
|
148
|
+
// Verify both spaces are federated with each other
|
|
149
|
+
const fedInfo1 = await holoSphere.getFederation(space1.spacename);
|
|
150
|
+
const fedInfo2 = await holoSphere.getFederation(space2.spacename);
|
|
151
|
+
|
|
152
|
+
expect(fedInfo1).toBeDefined();
|
|
153
|
+
expect(fedInfo2).toBeDefined();
|
|
154
|
+
expect(fedInfo1.federation).toContain(space2.spacename);
|
|
155
|
+
expect(fedInfo2.notify).toContain(space1.spacename);
|
|
156
|
+
}, 10000);
|
|
157
|
+
|
|
158
|
+
test('should prevent duplicate federation relationships', async () => {
|
|
159
|
+
// Login to space1
|
|
160
|
+
await holoSphere.login(space1.spacename, space1.password);
|
|
161
|
+
|
|
162
|
+
// Create initial federation
|
|
163
|
+
await holoSphere.federate(space1.spacename, space2.spacename);
|
|
164
|
+
|
|
165
|
+
// Verify federation exists
|
|
166
|
+
const fedInfo = await holoSphere.getFederation(space1.spacename);
|
|
167
|
+
expect(fedInfo).toBeDefined();
|
|
168
|
+
expect(fedInfo.federation).toBeDefined();
|
|
169
|
+
expect(fedInfo.federation).toContain(space2.spacename);
|
|
170
|
+
|
|
171
|
+
// Attempt to create duplicate federation
|
|
172
|
+
await expect(holoSphere.federate(space1.spacename, space2.spacename))
|
|
173
|
+
.rejects.toThrow('Federation already exists');
|
|
174
|
+
}, 15000);
|
|
175
|
+
|
|
176
|
+
test('should handle federation data propagation', async () => {
|
|
177
|
+
// Login to space1
|
|
178
|
+
await holoSphere.login(space1.spacename, space1.password);
|
|
179
|
+
|
|
180
|
+
const testData = {
|
|
181
|
+
id: 'test1',
|
|
182
|
+
name: 'Test Item',
|
|
183
|
+
data: 'federated content'
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Set up federation
|
|
187
|
+
await holoSphere.federate(space1.spacename, space2.spacename);
|
|
188
|
+
|
|
189
|
+
// Login to space2 with strict instance
|
|
190
|
+
await strictHoloSphere.login(space2.spacename, space2.password);
|
|
191
|
+
|
|
192
|
+
// Put data in first space
|
|
193
|
+
await holoSphere.put(testHolon, testLens, testData);
|
|
194
|
+
|
|
195
|
+
// Verify data was propagated to federated space
|
|
196
|
+
const federatedData = await strictHoloSphere.get(testHolon, testLens, testData.id);
|
|
197
|
+
expect(federatedData).toBeDefined();
|
|
198
|
+
expect(federatedData).not.toBeNull();
|
|
199
|
+
expect(federatedData.data).toEqual(testData.data);
|
|
200
|
+
expect(federatedData.federation).toBeDefined();
|
|
201
|
+
expect(federatedData.federation.origin).toEqual(space1.spacename);
|
|
202
|
+
expect(federatedData.federation.timestamp).toBeGreaterThan(0);
|
|
203
|
+
}, 20000);
|
|
204
|
+
|
|
205
|
+
test('should handle different getFederated modalities', async () => {
|
|
206
|
+
// Clean up any existing test data first
|
|
207
|
+
await holoSphere.deleteAll(testHolon, testLens);
|
|
208
|
+
await strictHoloSphere.deleteAll(testHolon, testLens);
|
|
209
|
+
|
|
210
|
+
// Set up federation using non-strict instance (already logged in as space1)
|
|
211
|
+
await holoSphere.federate(space1.spacename, space2.spacename);
|
|
212
|
+
|
|
213
|
+
// Login to space2 with strict instance
|
|
214
|
+
await strictHoloSphere.login(space2.spacename, space2.password);
|
|
215
|
+
|
|
216
|
+
// Test data with overlapping IDs and different fields
|
|
217
|
+
const testData1 = {
|
|
218
|
+
id: 'user1',
|
|
219
|
+
name: 'User One',
|
|
220
|
+
received: 10,
|
|
221
|
+
sent: 5,
|
|
222
|
+
wants: ['item1', 'item2'],
|
|
223
|
+
offers: ['service1']
|
|
224
|
+
};
|
|
225
|
+
const testData2 = {
|
|
226
|
+
id: 'user1', // Same ID as testData1
|
|
227
|
+
name: 'User One Updated',
|
|
228
|
+
received: 15,
|
|
229
|
+
sent: 8,
|
|
230
|
+
wants: ['item3'],
|
|
231
|
+
offers: ['service2']
|
|
232
|
+
};
|
|
233
|
+
const testData3 = {
|
|
234
|
+
id: 'user2',
|
|
235
|
+
name: 'User Two',
|
|
236
|
+
received: 20,
|
|
237
|
+
sent: 12,
|
|
238
|
+
wants: ['item4'],
|
|
239
|
+
offers: ['service3']
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// Put data using both instances and wait between puts
|
|
243
|
+
console.log('Putting test data 1:', JSON.stringify(testData1, null, 2));
|
|
244
|
+
await holoSphere.put(testHolon, testLens, testData1);
|
|
245
|
+
|
|
246
|
+
console.log('Putting test data 2:', JSON.stringify(testData2, null, 2));
|
|
247
|
+
await strictHoloSphere.put(testHolon, testLens, testData2);
|
|
248
|
+
|
|
249
|
+
console.log('Putting test data 3:', JSON.stringify(testData3, null, 2));
|
|
250
|
+
await holoSphere.put(testHolon, testLens, testData3);
|
|
251
|
+
|
|
252
|
+
// Test 1: Simple concatenation without deduplication
|
|
253
|
+
const concatenatedResults = await holoSphere.getFederated(testHolon, testLens, {
|
|
254
|
+
aggregate: false,
|
|
255
|
+
removeDuplicates: false
|
|
256
|
+
});
|
|
257
|
+
console.log('Concatenated results:', JSON.stringify(concatenatedResults, null, 2));
|
|
258
|
+
|
|
259
|
+
// Verify we have all items including duplicates
|
|
260
|
+
expect(concatenatedResults.filter(item => item.id === 'user1').length).toBeGreaterThanOrEqual(1);
|
|
261
|
+
expect(concatenatedResults.filter(item => item.id === 'user2').length).toBe(1);
|
|
262
|
+
|
|
263
|
+
// Test 2: With deduplication
|
|
264
|
+
const dedupedResults = await strictHoloSphere.getFederated(testHolon, testLens, {
|
|
265
|
+
aggregate: false,
|
|
266
|
+
removeDuplicates: true
|
|
267
|
+
});
|
|
268
|
+
console.log('Deduped results:', JSON.stringify(dedupedResults, null, 2));
|
|
269
|
+
expect(dedupedResults.length).toBe(2);
|
|
270
|
+
const user1Deduped = dedupedResults.find(item => item.id === 'user1');
|
|
271
|
+
expect(user1Deduped.name).toBe('User One Updated');
|
|
272
|
+
|
|
273
|
+
// Test 3: With aggregation
|
|
274
|
+
const aggregationOptions = {
|
|
275
|
+
aggregate: true,
|
|
276
|
+
idField: 'id',
|
|
277
|
+
sumFields: ['received', 'sent'],
|
|
278
|
+
concatArrays: ['wants', 'offers'],
|
|
279
|
+
removeDuplicates: true
|
|
280
|
+
};
|
|
281
|
+
console.log('Aggregation options:', JSON.stringify(aggregationOptions, null, 2));
|
|
282
|
+
const aggregatedResults = await holoSphere.getFederated(testHolon, testLens, aggregationOptions);
|
|
283
|
+
console.log('Aggregated results:', JSON.stringify(aggregatedResults, null, 2));
|
|
284
|
+
|
|
285
|
+
// Sort results by ID for consistent testing
|
|
286
|
+
const sortedResults = aggregatedResults.sort((a, b) => a.id.localeCompare(b.id));
|
|
287
|
+
expect(sortedResults.length).toBe(2);
|
|
288
|
+
|
|
289
|
+
const user1Aggregated = sortedResults.find(item => item.id === 'user1');
|
|
290
|
+
expect(user1Aggregated).toBeDefined();
|
|
291
|
+
expect(user1Aggregated.received).toBe(15); // Latest value
|
|
292
|
+
expect(user1Aggregated.sent).toBe(8); // Latest value
|
|
293
|
+
expect(user1Aggregated.wants).toEqual(['item3']); // Latest value
|
|
294
|
+
expect(user1Aggregated.offers).toEqual(['service2']); // Latest value
|
|
295
|
+
|
|
296
|
+
const user2Aggregated = sortedResults.find(item => item.id === 'user2');
|
|
297
|
+
expect(user2Aggregated).toBeDefined();
|
|
298
|
+
expect(user2Aggregated.received).toBe(20);
|
|
299
|
+
expect(user2Aggregated.sent).toBe(12);
|
|
300
|
+
expect(user2Aggregated.wants).toEqual(['item4']);
|
|
301
|
+
expect(user2Aggregated.offers).toEqual(['service3']);
|
|
302
|
+
}, 60000);
|
|
303
|
+
|
|
304
|
+
test('should handle unfederation', async () => {
|
|
305
|
+
// Login to space1
|
|
306
|
+
await holoSphere.login(space1.spacename, space1.password);
|
|
307
|
+
|
|
308
|
+
// Set up federation
|
|
309
|
+
await holoSphere.federate(space1.spacename, space2.spacename);
|
|
310
|
+
|
|
311
|
+
// Verify federation exists
|
|
312
|
+
let fedInfo1 = await holoSphere.getFederation(space1.spacename);
|
|
313
|
+
expect(fedInfo1).toBeDefined();
|
|
314
|
+
expect(fedInfo1.federation).toBeDefined();
|
|
315
|
+
expect(fedInfo1.federation).toContain(space2.spacename);
|
|
316
|
+
|
|
317
|
+
// Remove federation
|
|
318
|
+
await holoSphere.unfederate(space1.spacename, space2.spacename);
|
|
319
|
+
|
|
320
|
+
// Verify federation is removed
|
|
321
|
+
fedInfo1 = await holoSphere.getFederation(space1.spacename);
|
|
322
|
+
const fedInfo2 = await holoSphere.getFederation(space2.spacename);
|
|
323
|
+
|
|
324
|
+
expect(fedInfo1).toBeDefined();
|
|
325
|
+
expect(fedInfo2).toBeDefined();
|
|
326
|
+
expect(fedInfo1.federation || []).not.toContain(space2.spacename);
|
|
327
|
+
expect(fedInfo2.notify || []).not.toContain(space1.spacename);
|
|
328
|
+
}, 20000);
|
|
329
|
+
|
|
330
|
+
afterEach(async () => {
|
|
331
|
+
// Clean up test data
|
|
332
|
+
await holoSphere.deleteAll(testHolon, testLens);
|
|
333
|
+
|
|
334
|
+
// Clean up federation data while still logged in
|
|
335
|
+
await holoSphere.deleteAllGlobal('federation');
|
|
336
|
+
|
|
337
|
+
// Logout from both instances
|
|
338
|
+
if (holoSphere.currentSpace) {
|
|
339
|
+
await holoSphere.logout();
|
|
340
|
+
}
|
|
341
|
+
if (strictHoloSphere.currentSpace) {
|
|
342
|
+
await strictHoloSphere.logout();
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
afterAll(async () => {
|
|
347
|
+
// Clean up test spaces
|
|
348
|
+
try {
|
|
349
|
+
await holoSphere.deleteGlobal('spaces', space1.spacename);
|
|
350
|
+
await holoSphere.deleteGlobal('spaces', space2.spacename);
|
|
351
|
+
} catch (error) {
|
|
352
|
+
console.error('Error during final cleanup:', error.message);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Clean up Gun instances
|
|
356
|
+
if (holoSphere.gun) {
|
|
357
|
+
holoSphere.gun.off();
|
|
358
|
+
}
|
|
359
|
+
if (strictHoloSphere.gun) {
|
|
360
|
+
strictHoloSphere.gun.off();
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
});
|