holosphere 1.1.4 → 1.1.5

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 CHANGED
@@ -14,6 +14,7 @@ class HoloSphere {
14
14
  * @param {Gun|null} gunInstance - The Gun instance to use.
15
15
  */
16
16
  constructor(appname, strict = false, openaikey = null, gunInstance = null) {
17
+ console.log('HoloSphere v1.1.8');
17
18
  this.appname = appname
18
19
  this.strict = strict;
19
20
  this.validator = new Ajv2019({
@@ -24,7 +25,7 @@ class HoloSphere {
24
25
 
25
26
  // Use provided Gun instance or create new one
26
27
  this.gun = gunInstance || Gun({
27
- peers: ['https://gun.holons.io/gun', 'https://59.src.eco/gun'],
28
+ peers: ['https://gun.holons.io/gun'],
28
29
  axe: false,
29
30
  // uuid: (content) => { // generate a unique id for each node
30
31
  // console.log('uuid', content);
@@ -65,68 +66,50 @@ class HoloSphere {
65
66
  throw new Error('setSchema: Schema must have a type field');
66
67
  }
67
68
 
68
- if (this.strict) {
69
- const metaSchema = {
70
- type: 'object',
71
- required: ['type', 'properties'],
69
+ const metaSchema = {
70
+ type: 'object',
71
+ required: ['type', 'properties'],
72
+ properties: {
73
+ type: { type: 'string' },
72
74
  properties: {
73
- type: { type: 'string' },
74
- properties: {
75
+ type: 'object',
76
+ additionalProperties: {
75
77
  type: 'object',
76
- additionalProperties: {
77
- type: 'object',
78
- required: ['type'],
79
- properties: {
80
- type: { type: 'string' }
81
- }
78
+ required: ['type'],
79
+ properties: {
80
+ type: { type: 'string' }
82
81
  }
83
- },
84
- required: {
85
- type: 'array',
86
- items: { type: 'string' }
87
82
  }
83
+ },
84
+ required: {
85
+ type: 'array',
86
+ items: { type: 'string' }
88
87
  }
89
- };
90
-
91
- const valid = this.validator.validate(metaSchema, schema);
92
- if (!valid) {
93
- throw new Error(`Invalid schema structure: ${JSON.stringify(this.validator.errors)}`);
94
- }
95
-
96
- if (!schema.properties || typeof schema.properties !== 'object') {
97
- throw new Error('Schema must have properties in strict mode');
98
88
  }
89
+ };
99
90
 
100
- if (!schema.required || !Array.isArray(schema.required) || schema.required.length === 0) {
101
- throw new Error('Schema must have required fields in strict mode');
102
- }
91
+ const valid = this.validator.validate(metaSchema, schema);
92
+ if (!valid) {
93
+ throw new Error(`Invalid schema structure: ${JSON.stringify(this.validator.errors)}`);
103
94
  }
104
95
 
105
- return new Promise((resolve, reject) => {
106
- try {
107
- const schemaString = JSON.stringify(schema);
108
- const schemaData = {
109
- schema: schemaString,
110
- timestamp: Date.now(),
111
- // Only set owner if there's an authenticated space
112
- ...(this.currentSpace && { owner: this.currentSpace.alias })
113
- };
96
+ if (!schema.properties || typeof schema.properties !== 'object') {
97
+ throw new Error('Schema must have properties in strict mode');
98
+ }
114
99
 
115
- this.gun.get(this.appname)
116
- .get(lens)
117
- .get('schema')
118
- .put(schemaData, ack => {
119
- if (ack.err) {
120
- reject(new Error(ack.err));
121
- } else {
122
- // Add small delay to ensure data is written
123
- setTimeout(() => resolve(true), 50);
124
- }
125
- });
126
- } catch (error) {
127
- reject(error);
128
- }
100
+ if (!schema.required || !Array.isArray(schema.required) || schema.required.length === 0) {
101
+ throw new Error('Schema must have required fields in strict mode');
102
+ }
103
+
104
+ // Store schema in global table with lens as key
105
+ await this.putGlobal('schemas', {
106
+ id: lens,
107
+ schema: schema,
108
+ timestamp: Date.now(),
109
+ owner: this.currentSpace?.alias
129
110
  });
111
+
112
+ return true;
130
113
  }
131
114
 
132
115
  /**
@@ -139,39 +122,12 @@ class HoloSphere {
139
122
  throw new Error('getSchema: Missing lens parameter');
140
123
  }
141
124
 
142
- return new Promise((resolve) => {
143
- let timeout = setTimeout(() => {
144
- console.warn('getSchema: Operation timed out');
145
- resolve(null);
146
- }, 5000);
147
-
148
- this.gun.get(this.appname)
149
- .get(lens)
150
- .get('schema')
151
- .once(data => {
152
- clearTimeout(timeout);
153
- if (!data) {
154
- resolve(null);
155
- return;
156
- }
125
+ const schemaData = await this.getGlobal('schemas', lens);
126
+ if (!schemaData || !schemaData.schema) {
127
+ return null;
128
+ }
157
129
 
158
- try {
159
- // Handle both new format and legacy format
160
- if (data.schema) {
161
- // New format with timestamp
162
- resolve(JSON.parse(data.schema));
163
- } else {
164
- // Legacy format or direct string
165
- const schemaStr = typeof data === 'string' ? data :
166
- Object.values(data).find(v => typeof v === 'string' && v.includes('"type":'));
167
- resolve(schemaStr ? JSON.parse(schemaStr) : null);
168
- }
169
- } catch (error) {
170
- console.error('getSchema: Error parsing schema:', error);
171
- resolve(null);
172
- }
173
- });
174
- });
130
+ return schemaData.schema;
175
131
  }
176
132
 
177
133
  // ================================ CONTENT FUNCTIONS ================================
@@ -567,11 +523,19 @@ class HoloSphere {
567
523
  * @returns {Promise<object>} - The parsed data.
568
524
  */
569
525
  async parse(rawData) {
526
+ let parsedData = {};
527
+
570
528
  if (!rawData) {
571
529
  throw new Error('parse: No data provided');
572
530
  }
573
531
 
574
532
  try {
533
+
534
+ if (typeof rawData === 'string') {
535
+ parsedData = await JSON.parse(rawData);
536
+ }
537
+
538
+
575
539
  if (rawData.soul) {
576
540
  const data = await this.getNodeRef(rawData.soul).once();
577
541
  if (!data) {
@@ -580,7 +544,7 @@ class HoloSphere {
580
544
  return JSON.parse(data);
581
545
  }
582
546
 
583
- let parsedData = {};
547
+
584
548
  if (typeof rawData === 'object' && rawData !== null) {
585
549
  if (rawData._ && rawData._["#"]) {
586
550
  const pathParts = rawData._['#'].split('/');
@@ -600,11 +564,10 @@ class HoloSphere {
600
564
  } else {
601
565
  parsedData = rawData;
602
566
  }
603
- } else {
604
- parsedData = JSON.parse(rawData);
605
567
  }
606
568
 
607
569
  return parsedData;
570
+
608
571
  } catch (error) {
609
572
  console.log("Parsing not a JSON, returning raw data", rawData);
610
573
  return rawData;
@@ -1423,23 +1386,27 @@ class HoloSphere {
1423
1386
  * @param {function} callback - The callback to execute on changes.
1424
1387
  */
1425
1388
  async subscribe(holon, lens, callback) {
1426
- const subscriptionId = this.generateSubscriptionId();
1427
- this.subscriptions[subscriptionId] = {
1428
- query: { holon, lens },
1429
- callback,
1430
- active: true
1431
- };
1432
-
1433
- // Add cleanup to ensure callback isn't called after unsubscribe
1389
+ const subscriptionId = this.generateId();
1390
+ this.subscriptions[subscriptionId] =
1391
+ this.gun.get(this.appname).get(holon).get(lens).map().on( async (data, key) => {
1392
+ if (data) {
1393
+ try {
1394
+ let parsed = await this.parse(data)
1395
+ callback(parsed, key)
1396
+ } catch (error) {
1397
+ console.error('Error in subscribe:', error);
1398
+ }
1399
+ }
1400
+ })
1434
1401
  return {
1435
1402
  unsubscribe: () => {
1436
- if (this.subscriptions[subscriptionId]) {
1437
- delete this.subscriptions[subscriptionId];
1438
- }
1403
+ this.gun.get(this.appname).get(holon).get(lens).map().off()
1404
+ delete this.subscriptions[subscriptionId];
1439
1405
  }
1440
- };
1406
+ }
1441
1407
  }
1442
1408
 
1409
+
1443
1410
  notifySubscribers(data) {
1444
1411
  Object.values(this.subscriptions).forEach(subscription => {
1445
1412
  if (subscription.active && this.matchesQuery(data, subscription.query)) {
@@ -1453,14 +1420,10 @@ class HoloSphere {
1453
1420
  return Date.now().toString(10) + Math.random().toString(2);
1454
1421
  }
1455
1422
 
1456
- generateSubscriptionId() {
1457
- return Date.now().toString(10) + Math.random().toString(2);
1458
- }
1459
-
1460
1423
  matchesQuery(data, query) {
1461
- return data && query &&
1462
- data.holon === query.holon &&
1463
- data.lens === query.lens;
1424
+ return data && query &&
1425
+ data.holon === query.holon &&
1426
+ data.lens === query.lens;
1464
1427
  }
1465
1428
 
1466
1429
  /**
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "holosphere",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "description": "Holonic Geospatial Communication Infrastructure",
5
5
  "main": "holosphere.js",
6
6
  "types": "holosphere.d.ts",
7
7
  "type": "module",
8
8
  "scripts": {
9
- "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
9
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js ./test",
10
10
  "build": "",
11
11
  "prepare": "npm run build"
12
12
  },
@@ -14,12 +14,14 @@
14
14
  "license": "GPL-3.0-or-later",
15
15
  "dependencies": {
16
16
  "ajv": "^8.12.0",
17
+ "axios": "^1.6.7",
17
18
  "dotenv": "^16.4.7",
18
19
  "gun": "^0.2020.1240",
19
20
  "h3-js": "^4.1.0",
20
21
  "openai": "^4.85.1"
21
22
  },
22
23
  "devDependencies": {
24
+ "@babel/preset-env": "^7.24.0",
23
25
  "jest": "^29.7.0",
24
26
  "jest-environment-node": "^29.7.0"
25
27
  },
@@ -28,6 +30,9 @@
28
30
  "transform": {},
29
31
  "moduleNameMapper": {
30
32
  "^(\\.{1,2}/.*)\\.js$": "$1"
31
- }
33
+ },
34
+ "setupFilesAfterEnv": [
35
+ "<rootDir>/test/jest.setup.js"
36
+ ]
32
37
  }
33
38
  }
@@ -0,0 +1,253 @@
1
+ import axios from 'axios';
2
+ import dotenv from 'dotenv';
3
+
4
+ dotenv.config();
5
+
6
+ const logError = (service, error) => {
7
+ const status = error.response?.status;
8
+ const message = error.response?.data?.message || error.message;
9
+ console.error(`${service} API Error:`, { status, message });
10
+ };
11
+
12
+ /**
13
+ * Fetch Carbon Sequestration Data (Using NASA POWER API)
14
+ */
15
+ export async function getCarbonSequestration(lat = 41.9, lon = 12.5) {
16
+ try {
17
+ const response = await axios.default.get(
18
+ `https://power.larc.nasa.gov/api/temporal/daily/point?parameters=T2M,PRECTOT,PS,WS2M&community=RE&longitude=${lon}&latitude=${lat}&start=20230101&end=20231231&format=JSON`
19
+ );
20
+ return response.data;
21
+ } catch (error) {
22
+ logError('Carbon Sequestration', error);
23
+ return null;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Fetch Soil Carbon Data (Using ISRIC World Soil Information)
29
+ */
30
+ export async function getSoilCarbon(lat = 41.9, lon = 12.5) {
31
+ try {
32
+ const response = await axios.default.get(
33
+ `https://rest.isric.org/soilgrids/v2.0/properties/query?lat=${lat}&lon=${lon}&property=soc&depth=0-30cm&value=mean`
34
+ );
35
+ return response.data;
36
+ } catch (error) {
37
+ logError('Soil Carbon', error);
38
+ return null;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Fetch Biodiversity Data (GBIF API)
44
+ */
45
+ export async function getBiodiversityData(countryCode = "ITA") {
46
+ try {
47
+ const response = await axios.default.get(`https://api.gbif.org/v1/occurrence/search?country=${countryCode}&limit=100`);
48
+ return response.data;
49
+ } catch (error) {
50
+ logError('Biodiversity', error);
51
+ return null;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Fetch Vegetation Cover Data (Using NASA MODIS Vegetation Index)
57
+ */
58
+ export async function getVegetationCover(lat = 41.9, lon = 12.5) {
59
+ try {
60
+ const response = await axios.default.get(
61
+ `https://modis.ornl.gov/rst/api/v1/MOD13Q1/subset?latitude=${lat}&longitude=${lon}&band=NDVI&startDate=2023-01-01&endDate=2023-12-31`,
62
+ {
63
+ headers: {
64
+ 'Accept': 'application/json'
65
+ }
66
+ }
67
+ );
68
+ return response.data;
69
+ } catch (error) {
70
+ logError('Vegetation Cover', error);
71
+ return null;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Fetch Air Quality Data (OpenWeather API)
77
+ */
78
+ export async function getAirQuality(lat = 41.9, lon = 12.5) {
79
+ try {
80
+ const response = await axios.default.get(
81
+ `https://api.openweathermap.org/data/2.5/air_pollution?lat=${lat}&lon=${lon}&appid=${process.env.OPENWEATHER_API_KEY}`
82
+ );
83
+ return response.data;
84
+ } catch (error) {
85
+ logError('Air Quality', error);
86
+ return null;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Fetch Water Retention Data (Using USGS Water Services)
92
+ */
93
+ export async function getWaterRetention(lat = 41.9, lon = 12.5) {
94
+ try {
95
+ const response = await axios.default.get(
96
+ `https://waterservices.usgs.gov/nwis/iv/?format=json&sites=11447650&siteStatus=active`,
97
+ {
98
+ headers: {
99
+ 'Accept': 'application/json'
100
+ }
101
+ }
102
+ );
103
+ return response.data;
104
+ } catch (error) {
105
+ logError('Water Retention', error);
106
+ return null;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Fetch Deforestation Data (Using World Bank Forest Data)
112
+ */
113
+ export async function getDeforestationData(countryCode = "ITA") {
114
+ try {
115
+ const response = await axios.default.get(
116
+ `https://api.worldbank.org/v2/country/${countryCode}/indicator/AG.LND.FRST.ZS?format=json`
117
+ );
118
+ return response.data;
119
+ } catch (error) {
120
+ logError('Deforestation', error);
121
+ return null;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Fetch Flood Risk Data (Using USGS Water Services)
127
+ */
128
+ export async function getFloodRisk(lat = 41.9, lon = 12.5) {
129
+ try {
130
+ const response = await axios.default.get(
131
+ `https://waterservices.usgs.gov/nwis/iv/?format=json&stateCd=CA&parameterCd=00065&siteStatus=active`,
132
+ {
133
+ headers: {
134
+ 'Accept': 'application/json'
135
+ }
136
+ }
137
+ );
138
+ return response.data;
139
+ } catch (error) {
140
+ logError('Flood Risk', error);
141
+ return null;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Fetch Food Security Data (World Bank API)
147
+ */
148
+ export async function getFoodSecurity(countryCode = "ITA") {
149
+ try {
150
+ const response = await axios.default.get(
151
+ `https://api.worldbank.org/v2/country/${countryCode}/indicator/SN.ITK.DEFC.ZS?format=json`
152
+ );
153
+ return response.data;
154
+ } catch (error) {
155
+ logError('Food Security', error);
156
+ return null;
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Fetch Local Employment Rate (World Bank API)
162
+ */
163
+ export async function getEmploymentRate(countryCode = "ITA") {
164
+ try {
165
+ const response = await axios.default.get(
166
+ `https://api.worldbank.org/v2/country/${countryCode}/indicator/SL.EMP.TOTL.SP.ZS?format=json`
167
+ );
168
+ return response.data;
169
+ } catch (error) {
170
+ logError('Employment Rate', error);
171
+ return null;
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Fetch Governance Score (World Bank API)
177
+ */
178
+ export async function getTransparencyScore(countryCode = "ITA") {
179
+ try {
180
+ const response = await axios.default.get(
181
+ `https://api.worldbank.org/v2/country/${countryCode}/indicator/GE.EST?format=json`
182
+ );
183
+ return response.data;
184
+ } catch (error) {
185
+ logError('Transparency Score', error);
186
+ return null;
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Fetch Blockchain Transactions (Etherscan API)
192
+ */
193
+ export async function getBlockchainTransactions(address = "0x0000000000000000000000000000000000000000") {
194
+ try {
195
+ const response = await axios.default.get(
196
+ `https://api.etherscan.io/api?module=account&action=txlist&address=${address}&startblock=0&endblock=99999999&sort=desc&apikey=${process.env.ETHERSCAN_API_KEY}`
197
+ );
198
+ return response.data;
199
+ } catch (error) {
200
+ logError('Blockchain Transactions', error);
201
+ return null;
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Fetch Circular Economy Data (Using World Bank Development Indicators)
207
+ */
208
+ export async function getCircularEconomyData(countryCode = "ITA") {
209
+ try {
210
+ const response = await axios.default.get(
211
+ `https://api.worldbank.org/v2/country/${countryCode}/indicator/EN.ATM.GHGT.KT.CE?format=json`
212
+ );
213
+ return response.data;
214
+ } catch (error) {
215
+ logError('Circular Economy', error);
216
+ return null;
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Fetch Renewable Energy Data (World Bank API)
222
+ */
223
+ export async function getRenewableEnergyData(countryCode = "ITA") {
224
+ try {
225
+ const response = await axios.default.get(
226
+ `https://api.worldbank.org/v2/country/${countryCode}/indicator/EG.FEC.RNEW.ZS?format=json`
227
+ );
228
+ return response.data;
229
+ } catch (error) {
230
+ logError('Renewable Energy', error);
231
+ return null;
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Fetch Climate Change Data (NOAA Climate API)
237
+ */
238
+ export async function getClimateChangeData(lat = 41.9, lon = 12.5) {
239
+ try {
240
+ const response = await axios.default.get(
241
+ `https://www.ncdc.noaa.gov/cdo-web/api/v2/data?datasetid=GHCND&latitude=${lat}&longitude=${lon}&startdate=2023-01-01&enddate=2023-12-31&limit=1000`,
242
+ {
243
+ headers: {
244
+ 'token': process.env.NOAA_API_KEY
245
+ }
246
+ }
247
+ );
248
+ return response.data;
249
+ } catch (error) {
250
+ logError('Climate Change', error);
251
+ return null;
252
+ }
253
+ }
@@ -0,0 +1,164 @@
1
+ import { jest } from '@jest/globals';
2
+
3
+ // Mock axios before importing the modules that use it
4
+ jest.mock('axios', () => ({
5
+ default: {
6
+ get: jest.fn()
7
+ }
8
+ }));
9
+
10
+ // Import axios after mocking
11
+ import axios from 'axios';
12
+
13
+ // Import the API functions
14
+ import {
15
+ getCarbonSequestration,
16
+ getSoilCarbon,
17
+ getBiodiversityData,
18
+ getVegetationCover,
19
+ getAirQuality,
20
+ getWaterRetention,
21
+ getDeforestationData,
22
+ getFloodRisk,
23
+ getFoodSecurity,
24
+ getEmploymentRate,
25
+ getTransparencyScore,
26
+ getBlockchainTransactions,
27
+ getCircularEconomyData,
28
+ getRenewableEnergyData,
29
+ getClimateChangeData
30
+ } from './environmentalApi.js';
31
+
32
+ describe('Environmental API Tests', () => {
33
+ // Increase timeout for real API calls
34
+ jest.setTimeout(60000);
35
+
36
+ const ROME_LAT = 41.9;
37
+ const ROME_LON = 12.5;
38
+ const COUNTRY_CODE = 'ITA';
39
+ const ETH_ADDRESS = '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe';
40
+
41
+ test('getCarbonSequestration returns NASA POWER data', async () => {
42
+ const result = await getCarbonSequestration(ROME_LAT, ROME_LON);
43
+ expect(result).not.toBeNull();
44
+ expect(result.messages).toBeDefined();
45
+ expect(result.parameters).toBeDefined();
46
+ expect(result.parameters).toHaveProperty('T2M');
47
+ expect(result.parameters).toHaveProperty('PRECTOT');
48
+ });
49
+
50
+ test('getSoilCarbon returns ISRIC soil data', async () => {
51
+ const result = await getSoilCarbon(ROME_LAT, ROME_LON);
52
+ expect(result).not.toBeNull();
53
+ expect(result.properties).toBeDefined();
54
+ expect(result.properties.soc).toBeDefined();
55
+ });
56
+
57
+ test('getBiodiversityData returns GBIF data', async () => {
58
+ const result = await getBiodiversityData(COUNTRY_CODE);
59
+ expect(result).not.toBeNull();
60
+ expect(result.count).toBeDefined();
61
+ expect(result.results).toBeDefined();
62
+ expect(Array.isArray(result.results)).toBeTruthy();
63
+ });
64
+
65
+ test('getVegetationCover returns MODIS NDVI data', async () => {
66
+ const result = await getVegetationCover(ROME_LAT, ROME_LON);
67
+ expect(result).not.toBeNull();
68
+ expect(result.subset || result.data).toBeDefined();
69
+ });
70
+
71
+ test('getAirQuality returns OpenWeather data', async () => {
72
+ const result = await getAirQuality(ROME_LAT, ROME_LON);
73
+ expect(result).not.toBeNull();
74
+ expect(result.list).toBeDefined();
75
+ expect(result.list[0].main).toBeDefined();
76
+ expect(result.list[0].components).toBeDefined();
77
+ });
78
+
79
+ test('getWaterRetention returns USGS data', async () => {
80
+ const result = await getWaterRetention(ROME_LAT, ROME_LON);
81
+ expect(result).not.toBeNull();
82
+ expect(result.value || result.timeSeries).toBeDefined();
83
+ });
84
+
85
+ test('getDeforestationData returns World Bank forest data', async () => {
86
+ const result = await getDeforestationData(COUNTRY_CODE);
87
+ expect(result).not.toBeNull();
88
+ expect(Array.isArray(result)).toBeTruthy();
89
+ if (result.length > 1) {
90
+ expect(result[1][0]).toHaveProperty('indicator');
91
+ expect(result[1][0].indicator.id).toBe('AG.LND.FRST.ZS');
92
+ }
93
+ });
94
+
95
+ test('getFloodRisk returns USGS gauge data', async () => {
96
+ const result = await getFloodRisk(ROME_LAT, ROME_LON);
97
+ expect(result).not.toBeNull();
98
+ expect(result.value || result.timeSeries).toBeDefined();
99
+ });
100
+
101
+ test('getFoodSecurity returns World Bank data', async () => {
102
+ const result = await getFoodSecurity(COUNTRY_CODE);
103
+ expect(result).not.toBeNull();
104
+ expect(Array.isArray(result)).toBeTruthy();
105
+ if (result.length > 1) {
106
+ expect(result[1][0]).toHaveProperty('indicator');
107
+ expect(result[1][0].indicator.id).toBe('SN.ITK.DEFC.ZS');
108
+ }
109
+ });
110
+
111
+ test('getEmploymentRate returns World Bank data', async () => {
112
+ const result = await getEmploymentRate(COUNTRY_CODE);
113
+ expect(result).not.toBeNull();
114
+ expect(Array.isArray(result)).toBeTruthy();
115
+ if (result.length > 1) {
116
+ expect(result[1][0]).toHaveProperty('indicator');
117
+ expect(result[1][0].indicator.id).toBe('SL.EMP.TOTL.SP.ZS');
118
+ }
119
+ });
120
+
121
+ test('getTransparencyScore returns World Bank governance data', async () => {
122
+ const result = await getTransparencyScore(COUNTRY_CODE);
123
+ expect(result).not.toBeNull();
124
+ expect(Array.isArray(result)).toBeTruthy();
125
+ if (result.length > 1) {
126
+ expect(result[1][0]).toHaveProperty('indicator');
127
+ expect(result[1][0].indicator.id).toBe('GE.EST');
128
+ }
129
+ });
130
+
131
+ test('getBlockchainTransactions returns Etherscan data', async () => {
132
+ const result = await getBlockchainTransactions(ETH_ADDRESS);
133
+ expect(result).not.toBeNull();
134
+ expect(result.status).toBeDefined();
135
+ expect(result.result).toBeDefined();
136
+ expect(Array.isArray(result.result)).toBeTruthy();
137
+ });
138
+
139
+ test('getCircularEconomyData returns World Bank emissions data', async () => {
140
+ const result = await getCircularEconomyData(COUNTRY_CODE);
141
+ expect(result).not.toBeNull();
142
+ expect(Array.isArray(result)).toBeTruthy();
143
+ if (result.length > 1) {
144
+ expect(result[1][0]).toHaveProperty('indicator');
145
+ expect(result[1][0].indicator.id).toBe('EN.ATM.GHGT.KT.CE');
146
+ }
147
+ });
148
+
149
+ test('getRenewableEnergyData returns World Bank energy data', async () => {
150
+ const result = await getRenewableEnergyData(COUNTRY_CODE);
151
+ expect(result).not.toBeNull();
152
+ expect(Array.isArray(result)).toBeTruthy();
153
+ if (result.length > 1) {
154
+ expect(result[1][0]).toHaveProperty('indicator');
155
+ expect(result[1][0].indicator.id).toBe('EG.FEC.RNEW.ZS');
156
+ }
157
+ });
158
+
159
+ test('getClimateChangeData returns NOAA data', async () => {
160
+ const result = await getClimateChangeData(ROME_LAT, ROME_LON);
161
+ expect(result).not.toBeNull();
162
+ expect(result.metadata || result.results).toBeDefined();
163
+ });
164
+ });
@@ -7,10 +7,13 @@ describe('HoloSphere', () => {
7
7
  spacename: 'testuser',
8
8
  password: 'testpass'
9
9
  };
10
- let holoSphere = new HoloSphere(testAppName, false);
10
+ let holoSphere;
11
+ let strictHoloSphere;
12
+
11
13
  beforeAll(async () => {
12
- // Initialize HoloSphere once for all tests
13
-
14
+ // Initialize HoloSphere instances once for all tests
15
+ holoSphere = new HoloSphere(testAppName, false);
16
+ strictHoloSphere = new HoloSphere(testAppName, true);
14
17
 
15
18
  // Set up test space and authenticate
16
19
  try {
@@ -26,15 +29,9 @@ describe('HoloSphere', () => {
26
29
  }
27
30
  }
28
31
 
29
- // Ensure we're logged in
32
+ // Ensure both instances are logged in
30
33
  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
- }
34
+ await strictHoloSphere.login(testCredentials.spacename, testCredentials.password);
38
35
  });
39
36
 
40
37
  describe('Constructor', () => {
@@ -45,11 +42,8 @@ describe('HoloSphere', () => {
45
42
  expect(holoSphere.openai).toBeUndefined();
46
43
  });
47
44
 
48
- test('should initialize with OpenAI when key provided', () => {
49
- const hsWithAI = new HoloSphere(testAppName, false, 'fake-key');
50
- expect(hsWithAI.openai).toBeDefined();
51
- // Clean up additional instance
52
- if (hsWithAI.gun) hsWithAI.gun.off();
45
+ test('should initialize with OpenAI', () => {
46
+ expect(new HoloSphere(testAppName, false, 'fake-key').openai).toBeDefined();
53
47
  });
54
48
  });
55
49
 
@@ -65,10 +59,8 @@ describe('HoloSphere', () => {
65
59
  };
66
60
 
67
61
  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
- }
62
+ // Clean up any existing schemas
63
+ await holoSphere.deleteAllGlobal('schemas');
72
64
  });
73
65
 
74
66
  test('should set and get schema', async () => {
@@ -84,11 +76,6 @@ describe('HoloSphere', () => {
84
76
  });
85
77
 
86
78
  test('should enforce strict mode schema validation', async () => {
87
- const strictHoloSphere = new HoloSphere(testAppName, true);
88
-
89
- // Login to the strict instance
90
- await strictHoloSphere.login(testCredentials.spacename, testCredentials.password);
91
-
92
79
  const invalidSchema = {
93
80
  type: 'object',
94
81
  properties: {
@@ -98,9 +85,6 @@ describe('HoloSphere', () => {
98
85
 
99
86
  await expect(strictHoloSphere.setSchema(testLens, invalidSchema))
100
87
  .rejects.toThrow();
101
-
102
- // Clean up
103
- await strictHoloSphere.logout();
104
88
  });
105
89
 
106
90
  test('should handle schema retrieval for non-existent lens', async () => {
@@ -110,10 +94,6 @@ describe('HoloSphere', () => {
110
94
 
111
95
  test('should maintain schema integrity across storage and retrieval', async () => {
112
96
  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
97
 
118
98
  // Create test schemas of increasing complexity
119
99
  const testSchemas = [
@@ -151,7 +131,6 @@ describe('HoloSphere', () => {
151
131
 
152
132
  // Store schema
153
133
  await strictHoloSphere.setSchema(testLensWithIndex, schema);
154
-
155
134
 
156
135
  // Retrieve schema
157
136
  const retrievedSchema = await strictHoloSphere.getSchema(testLensWithIndex);
@@ -187,17 +166,8 @@ describe('HoloSphere', () => {
187
166
 
188
167
  // Clean up after each schema test
189
168
  await strictHoloSphere.deleteAll('testHolon', testLensWithIndex);
190
- await strictHoloSphere.gun.get(strictHoloSphere.appname)
191
- .get(testLensWithIndex)
192
- .get('schema')
193
- .put(null);
194
169
  }
195
-
196
- // Clean up the strict instance
197
- if (strictHoloSphere.gun) {
198
- await strictHoloSphere.logout();
199
- }
200
- }, 10000); // Increase timeout to 10 seconds
170
+ }, 10000);
201
171
 
202
172
  test('should handle concurrent schema operations', async () => {
203
173
  const baseLens = 'concurrentSchemaTest';
@@ -272,10 +242,7 @@ describe('HoloSphere', () => {
272
242
 
273
243
  afterEach(async () => {
274
244
  // Clean up schemas after each test
275
- await holoSphere.gun.get(holoSphere.appname)
276
- .get(testLens)
277
- .get('schema')
278
- .put(null);
245
+ await holoSphere.deleteAllGlobal('schemas');
279
246
  });
280
247
  });
281
248
 
@@ -355,11 +322,6 @@ describe('HoloSphere', () => {
355
322
  });
356
323
 
357
324
  test('should enforce strict mode data validation', async () => {
358
- const strictHoloSphere = new HoloSphere(testAppName, true);
359
-
360
- // Login to the strict instance
361
- await strictHoloSphere.login(testCredentials.spacename, testCredentials.password);
362
-
363
325
  // Define schema for strict mode tests
364
326
  const strictSchema = {
365
327
  type: 'object',
@@ -376,9 +338,6 @@ describe('HoloSphere', () => {
376
338
  // Try to put data without schema in strict mode
377
339
  await expect(strictHoloSphere.put(testHolon, 'no-schema-lens', validData))
378
340
  .rejects.toThrow('Schema required in strict mode');
379
-
380
- // Clean up
381
- await strictHoloSphere.logout();
382
341
  });
383
342
 
384
343
  test('should maintain content integrity in holon storage', async () => {
@@ -1015,14 +974,13 @@ describe('HoloSphere', () => {
1015
974
 
1016
975
  describe('OpenAI Integration', () => {
1017
976
  test('should handle missing OpenAI key', async () => {
1018
- const noAIHoloSphere = new HoloSphere('test');
1019
- const result = await noAIHoloSphere.summarize('test content');
1020
- expect(result).toBe('OpenAI not initialized, please specify the API key in the constructor.');
977
+ expect(await holoSphere.summarize('test content'))
978
+ .toBe('OpenAI not initialized, please specify the API key in the constructor.');
1021
979
  });
1022
980
 
1023
981
  test.skip('should summarize content with valid OpenAI key', async () => {
1024
- const hsWithAI = new HoloSphere('test', false, process.env.OPENAI_API_KEY);
1025
- const summary = await hsWithAI.summarize('Test content to summarize');
982
+ const summary = await new HoloSphere('test', false, process.env.OPENAI_API_KEY)
983
+ .summarize('Test content to summarize');
1026
984
  expect(typeof summary).toBe('string');
1027
985
  expect(summary.length).toBeGreaterThan(0);
1028
986
  });
@@ -1038,13 +996,21 @@ describe('HoloSphere', () => {
1038
996
  await holoSphere.deleteAllGlobal('testTable');
1039
997
  await holoSphere.deleteAllGlobal('testGlobalTable');
1040
998
  await holoSphere.deleteAllGlobal('concurrentGlobalTest');
999
+ await holoSphere.deleteAllGlobal('schemas');
1041
1000
 
1042
1001
  // Clean up test space
1043
1002
  await holoSphere.deleteGlobal('spaces', testCredentials.spacename);
1044
1003
 
1045
- // Logout
1004
+ // Logout both instances
1046
1005
  if (holoSphere.currentSpace) {
1047
1006
  await holoSphere.logout();
1048
1007
  }
1008
+ if (strictHoloSphere.currentSpace) {
1009
+ await strictHoloSphere.logout();
1010
+ }
1011
+
1012
+ // Clean up Gun instances
1013
+ if (holoSphere.gun) holoSphere.gun.off();
1014
+ if (strictHoloSphere.gun) strictHoloSphere.gun.off();
1049
1015
  });
1050
1016
  });
@@ -0,0 +1,5 @@
1
+
2
+ // Mock process.env
3
+ process.env.OPENWEATHER_API_KEY = 'test-api-key';
4
+ process.env.ETHERSCAN_API_KEY = 'test-api-key';
5
+ process.env.NOAA_API_KEY = 'test-api-key';