@olane/o-node 0.7.12 → 0.7.13-alpha.1

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.
Files changed (57) hide show
  1. package/dist/src/connection/interfaces/o-node-connection-manager.config.d.ts +1 -0
  2. package/dist/src/connection/interfaces/o-node-connection-manager.config.d.ts.map +1 -1
  3. package/dist/src/connection/o-node-connection.d.ts +0 -1
  4. package/dist/src/connection/o-node-connection.d.ts.map +1 -1
  5. package/dist/src/connection/o-node-connection.js +0 -8
  6. package/dist/src/connection/o-node-connection.manager.d.ts +33 -4
  7. package/dist/src/connection/o-node-connection.manager.d.ts.map +1 -1
  8. package/dist/src/connection/o-node-connection.manager.js +153 -44
  9. package/dist/src/connection/stream-handler.d.ts.map +1 -1
  10. package/dist/src/connection/stream-handler.js +0 -2
  11. package/dist/src/managers/o-connection-heartbeat.manager.d.ts.map +1 -1
  12. package/dist/src/managers/o-connection-heartbeat.manager.js +15 -1
  13. package/dist/src/managers/o-reconnection.manager.d.ts.map +1 -1
  14. package/dist/src/managers/o-reconnection.manager.js +12 -7
  15. package/dist/src/o-node.d.ts +5 -0
  16. package/dist/src/o-node.d.ts.map +1 -1
  17. package/dist/src/o-node.js +46 -8
  18. package/dist/src/o-node.tool.d.ts.map +1 -1
  19. package/dist/src/o-node.tool.js +5 -0
  20. package/dist/src/router/o-node.router.d.ts.map +1 -1
  21. package/dist/src/router/o-node.router.js +16 -6
  22. package/dist/src/router/o-node.routing-policy.d.ts.map +1 -1
  23. package/dist/src/router/o-node.routing-policy.js +4 -0
  24. package/dist/test/connection-management.spec.d.ts +2 -0
  25. package/dist/test/connection-management.spec.d.ts.map +1 -0
  26. package/dist/test/connection-management.spec.js +370 -0
  27. package/dist/test/helpers/connection-spy.d.ts +124 -0
  28. package/dist/test/helpers/connection-spy.d.ts.map +1 -0
  29. package/dist/test/helpers/connection-spy.js +229 -0
  30. package/dist/test/helpers/index.d.ts +6 -0
  31. package/dist/test/helpers/index.d.ts.map +1 -0
  32. package/dist/test/helpers/index.js +12 -0
  33. package/dist/test/helpers/network-builder.d.ts +109 -0
  34. package/dist/test/helpers/network-builder.d.ts.map +1 -0
  35. package/dist/test/helpers/network-builder.js +309 -0
  36. package/dist/test/helpers/simple-node-builder.d.ts +50 -0
  37. package/dist/test/helpers/simple-node-builder.d.ts.map +1 -0
  38. package/dist/test/helpers/simple-node-builder.js +66 -0
  39. package/dist/test/helpers/test-environment.d.ts +140 -0
  40. package/dist/test/helpers/test-environment.d.ts.map +1 -0
  41. package/dist/test/helpers/test-environment.js +184 -0
  42. package/dist/test/helpers/test-node.tool.d.ts +31 -0
  43. package/dist/test/helpers/test-node.tool.d.ts.map +1 -1
  44. package/dist/test/helpers/test-node.tool.js +49 -0
  45. package/dist/test/network-communication.spec.d.ts +2 -0
  46. package/dist/test/network-communication.spec.d.ts.map +1 -0
  47. package/dist/test/network-communication.spec.js +256 -0
  48. package/dist/test/o-node.spec.d.ts +2 -0
  49. package/dist/test/o-node.spec.d.ts.map +1 -0
  50. package/dist/test/o-node.spec.js +247 -0
  51. package/dist/test/parent-child-registration.spec.d.ts +2 -0
  52. package/dist/test/parent-child-registration.spec.d.ts.map +1 -0
  53. package/dist/test/parent-child-registration.spec.js +177 -0
  54. package/dist/test/search-resolver.spec.d.ts +2 -0
  55. package/dist/test/search-resolver.spec.d.ts.map +1 -0
  56. package/dist/test/search-resolver.spec.js +648 -0
  57. package/package.json +12 -7
@@ -11,6 +11,11 @@ import { StreamHandler } from './connection/stream-handler.js';
11
11
  */
12
12
  export class oNodeTool extends oTool(oServerNode) {
13
13
  async handleProtocol(address) {
14
+ const protocols = this.p2pNode.getProtocols();
15
+ if (protocols.find((p) => p === address.protocol)) {
16
+ // already handling
17
+ return;
18
+ }
14
19
  this.logger.debug('Handling protocol: ' + address.protocol);
15
20
  await this.p2pNode.handle(address.protocol, this.handleStream.bind(this), {
16
21
  maxInboundStreams: 10000,
@@ -1 +1 @@
1
- {"version":3,"file":"o-node.router.d.ts","sourceRoot":"","sources":["../../../src/router/o-node.router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAML,cAAc,EAGd,aAAa,EACd,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAM5C,qBAAa,WAAY,SAAQ,WAAW;IAC1C,OAAO,CAAC,aAAa,CAAqB;;IAO1C;;;;;;;;OAQG;cACa,OAAO,CACrB,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,KAAK,GACV,OAAO,CAAC,GAAG,CAAC;IA6Bf;;;OAGG;YACW,kBAAkB;IA8DhC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAS5B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAWhC;;OAEG;YACW,eAAe;IAsC7B;;;OAGG;IACG,SAAS,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC,aAAa,CAAC;IAyB3E;;;OAGG;IACH,UAAU,CAAC,qBAAqB,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO;CAGtE"}
1
+ {"version":3,"file":"o-node.router.d.ts","sourceRoot":"","sources":["../../../src/router/o-node.router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAML,cAAc,EAGd,aAAa,EACd,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAM5C,qBAAa,WAAY,SAAQ,WAAW;IAC1C,OAAO,CAAC,aAAa,CAAqB;;IAO1C;;;;;;;;OAQG;cACa,OAAO,CACrB,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,KAAK,GACV,OAAO,CAAC,GAAG,CAAC;IA6Bf;;;OAGG;YACW,kBAAkB;IA8DhC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAS5B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAWhC;;OAEG;YACW,eAAe;IAsC7B;;;OAGG;IACG,SAAS,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC,aAAa,CAAC;IAmC3E;;;OAGG;IACH,UAAU,CAAC,qBAAqB,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO;CAGtE"}
@@ -141,7 +141,7 @@ export class oNodeRouter extends oToolRouter {
141
141
  }
142
142
  catch (error) {
143
143
  if (error?.name === 'UnsupportedProtocolError') {
144
- throw new oError(oErrorCodes.NOT_FOUND, 'Address not found');
144
+ throw new oError(oErrorCodes.NOT_FOUND, 'Address not found: ' + address.value);
145
145
  }
146
146
  throw error;
147
147
  }
@@ -151,17 +151,27 @@ export class oNodeRouter extends oToolRouter {
151
151
  * First checks routing policy for external routing, then applies resolver chain.
152
152
  */
153
153
  async translate(address, node) {
154
- // Check if external routing is needed
155
- const externalRoute = this.routingPolicy.getExternalRoutingStrategy(address, node);
156
- if (externalRoute) {
157
- return externalRoute;
158
- }
159
154
  // Apply resolver chain for internal routing
155
+ if (!node.parent && !node.leader && address.transports?.length > 0) {
156
+ // independent node
157
+ return {
158
+ nextHopAddress: address,
159
+ targetAddress: address,
160
+ };
161
+ }
160
162
  const { nextHopAddress, targetAddress, requestOverride } = await this.addressResolution.resolve({
161
163
  address,
162
164
  node,
163
165
  targetAddress: address,
164
166
  });
167
+ // if we defaulted back to the leader
168
+ if (nextHopAddress.value === oAddress.leader().value) {
169
+ // Check if external routing is needed for leader routing
170
+ const externalRoute = this.routingPolicy.getExternalRoutingStrategy(address, node);
171
+ if (externalRoute) {
172
+ return externalRoute;
173
+ }
174
+ }
165
175
  return {
166
176
  nextHopAddress,
167
177
  targetAddress: targetAddress,
@@ -1 +1 @@
1
- {"version":3,"file":"o-node.routing-policy.d.ts","sourceRoot":"","sources":["../../../src/router/o-node.routing-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAG1C;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,cAAc;IACpD;;;;;;;;;OASG;IACH,iBAAiB,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO;IAuB1D;;;;;;;;OAQG;IACH,0BAA0B,CACxB,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,KAAK,GACV,aAAa,GAAG,IAAI;CAmBxB"}
1
+ {"version":3,"file":"o-node.routing-policy.d.ts","sourceRoot":"","sources":["../../../src/router/o-node.routing-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAG1C;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,cAAc;IACpD;;;;;;;;;OASG;IACH,iBAAiB,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO;IA4B1D;;;;;;;;OAQG;IACH,0BAA0B,CACxB,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,KAAK,GACV,aAAa,GAAG,IAAI;CAmBxB"}
@@ -21,6 +21,10 @@ export class oNodeRoutingPolicy extends oRoutingPolicy {
21
21
  if (node.hierarchyManager.parents.some((p) => p.equals(address))) {
22
22
  return true;
23
23
  }
24
+ // if we are trying to connect to a child, it's internal
25
+ if (node.hierarchyManager.children.some((p) => p.equals(address))) {
26
+ return true;
27
+ }
24
28
  if (nodeAddress.paths.indexOf(oAddress.leader().paths) !== -1 && // if the address has a leader
25
29
  nodeAddress.libp2pTransports?.length > 0) {
26
30
  // transports are provided, let's see if they match our known leaders
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=connection-management.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-management.spec.d.ts","sourceRoot":"","sources":["../../test/connection-management.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,370 @@
1
+ import { expect } from 'chai';
2
+ import { TestEnvironment } from './helpers/index.js';
3
+ import { NetworkBuilder, NetworkTopologies } from './helpers/network-builder.js';
4
+ import { createConnectionSpy } from './helpers/connection-spy.js';
5
+ import { oNodeAddress } from '../src/router/o-node.address.js';
6
+ import { oNodeTransport } from '../src/index.js';
7
+ describe('Connection Management', () => {
8
+ const env = new TestEnvironment();
9
+ let builder;
10
+ afterEach(async () => {
11
+ if (builder) {
12
+ await builder.cleanup();
13
+ }
14
+ await env.cleanup();
15
+ });
16
+ describe('Connection Pooling', () => {
17
+ it('should cache and reuse connections', async () => {
18
+ builder = await NetworkTopologies.twoNode();
19
+ const leader = builder.getNode('o://leader');
20
+ const child = builder.getNode('o://child');
21
+ const spy = createConnectionSpy(leader);
22
+ spy.start();
23
+ // Make first request (establishes connection)
24
+ await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
25
+ method: 'ping',
26
+ params: { message: 'first' },
27
+ });
28
+ const connectionsAfterFirst = spy.getSummary().currentConnections;
29
+ // Make second request (should reuse connection)
30
+ await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
31
+ method: 'ping',
32
+ params: { message: 'second' },
33
+ });
34
+ const connectionsAfterSecond = spy.getSummary().currentConnections;
35
+ // Connection count should remain the same (reused)
36
+ expect(connectionsAfterFirst).to.equal(connectionsAfterSecond);
37
+ expect(connectionsAfterFirst).to.be.greaterThan(0);
38
+ spy.stop();
39
+ });
40
+ it('should maintain separate connections to different nodes', async () => {
41
+ builder = await NetworkTopologies.fiveNode();
42
+ const leader = builder.getNode('o://leader');
43
+ const parent1 = builder.getNode('o://parent1');
44
+ const parent2 = builder.getNode('o://parent2');
45
+ const spy = createConnectionSpy(leader);
46
+ spy.start();
47
+ // Call different nodes
48
+ await leader.use(parent1.address, {
49
+ method: 'echo',
50
+ params: { message: 'to parent1' },
51
+ });
52
+ await leader.use(parent2.address, {
53
+ method: 'echo',
54
+ params: { message: 'to parent2' },
55
+ });
56
+ const stats = spy.getConnectionStats();
57
+ // Should have connections to both parents
58
+ expect(stats.length).to.be.greaterThan(1);
59
+ spy.stop();
60
+ });
61
+ it('should handle connection pool efficiently under load', async () => {
62
+ builder = await NetworkTopologies.fiveNode();
63
+ const leader = builder.getNode('o://leader');
64
+ const child1 = builder.getNode('o://child1');
65
+ const child2 = builder.getNode('o://child2');
66
+ const spy = createConnectionSpy(leader);
67
+ spy.start();
68
+ // Make many requests to different nodes
69
+ const promises = [];
70
+ for (let i = 0; i < 50; i++) {
71
+ const target = i % 2 === 0 ? child1 : child2;
72
+ promises.push(leader.use(target.address, {
73
+ method: 'echo',
74
+ params: { message: `request-${i}` },
75
+ }));
76
+ }
77
+ await Promise.all(promises);
78
+ const summary = spy.getSummary();
79
+ // Connection count should be reasonable (not 50)
80
+ expect(summary.currentConnections).to.be.lessThan(10);
81
+ expect(summary.currentConnections).to.be.greaterThan(0);
82
+ spy.stop();
83
+ });
84
+ });
85
+ describe('Connection Status', () => {
86
+ it('should report correct connection status', async () => {
87
+ builder = await NetworkTopologies.twoNode();
88
+ const leader = builder.getNode('o://leader');
89
+ const child = builder.getNode('o://child');
90
+ const spy = createConnectionSpy(leader);
91
+ spy.start();
92
+ // Establish connection
93
+ await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
94
+ method: 'echo',
95
+ params: { message: 'test' },
96
+ });
97
+ const stats = spy.getConnectionStats();
98
+ expect(stats.length).to.be.greaterThan(0);
99
+ const connection = stats[0];
100
+ expect(connection.status).to.equal('open');
101
+ expect(connection.peerId).to.be.a('string');
102
+ expect(connection.remoteAddr).to.be.a('string');
103
+ spy.stop();
104
+ });
105
+ it('should detect open connections', async () => {
106
+ builder = await NetworkTopologies.twoNode();
107
+ const leader = builder.getNode('o://leader');
108
+ const child = builder.getNode('o://child');
109
+ // Get child's peer ID
110
+ const childPeerId = child.address.libp2pTransports[0].toPeerId();
111
+ const spy = createConnectionSpy(leader);
112
+ spy.start();
113
+ // Establish connection
114
+ await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
115
+ method: 'echo',
116
+ params: { message: 'test' },
117
+ });
118
+ // Verify connection exists
119
+ const hasConnection = spy.hasConnectionToPeer(childPeerId);
120
+ expect(hasConnection).to.be.true;
121
+ spy.stop();
122
+ });
123
+ });
124
+ describe('Connection Validation', () => {
125
+ it('should validate connection before transmission', async () => {
126
+ builder = await NetworkTopologies.twoNode();
127
+ const leader = builder.getNode('o://leader');
128
+ const child = builder.getNode('o://child');
129
+ // Valid connection should work
130
+ const response = await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
131
+ method: 'echo',
132
+ params: { message: 'test' },
133
+ });
134
+ expect(response.result.success).to.be.true;
135
+ });
136
+ it('should handle connection to unreachable node', async () => {
137
+ builder = new NetworkBuilder();
138
+ const leader = await builder.addNode('o://leader');
139
+ // Create address to non-existent node
140
+ const fakeAddress = new oNodeAddress('o://nonexistent', [
141
+ new oNodeTransport('/ip4/127.0.0.1/tcp/4099'),
142
+ ]);
143
+ // Attempt to connect should fail gracefully
144
+ await leader.use(fakeAddress, {
145
+ method: 'echo',
146
+ params: { message: 'test' },
147
+ }).catch((err) => {
148
+ expect(err.message).to.be.equal('Unable to extract peer ID from address: o://nonexistent');
149
+ });
150
+ });
151
+ it('should verify connection is open before use', async () => {
152
+ builder = await NetworkTopologies.twoNode();
153
+ const leader = builder.getNode('o://leader');
154
+ const child = builder.getNode('o://child');
155
+ const spy = createConnectionSpy(leader);
156
+ spy.start();
157
+ // Make request
158
+ await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
159
+ method: 'echo',
160
+ params: { message: 'test' },
161
+ });
162
+ // Verify connection is open
163
+ const stats = spy.getConnectionStats();
164
+ expect(stats.length).to.be.greaterThan(0);
165
+ expect(stats[0].status).to.equal('open');
166
+ spy.stop();
167
+ });
168
+ });
169
+ describe('Connection Recovery', () => {
170
+ it('should handle transient connection errors', async () => {
171
+ builder = await NetworkTopologies.twoNode();
172
+ const leader = builder.getNode('o://leader');
173
+ const child = builder.getNode('o://child');
174
+ // Make successful request
175
+ const response1 = await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
176
+ method: 'echo',
177
+ params: { message: 'before' },
178
+ });
179
+ expect(response1.result.success).to.be.true;
180
+ // Simulate brief disconnection by stopping and restarting child
181
+ await child.stop();
182
+ await new Promise((resolve) => setTimeout(resolve, 100));
183
+ await child.start();
184
+ // Wait for reconnection
185
+ await new Promise((resolve) => setTimeout(resolve, 200));
186
+ // Subsequent request should eventually work
187
+ // Note: May need retry logic depending on implementation
188
+ const response2 = await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
189
+ method: 'echo',
190
+ params: { message: 'after' },
191
+ });
192
+ // This may fail if connection not re-established
193
+ // Test verifies graceful error handling
194
+ if (response2.result.success) {
195
+ expect(response2.result.data.message).to.equal('after');
196
+ }
197
+ else {
198
+ expect(response2.result.error).to.exist;
199
+ }
200
+ });
201
+ it('should maintain other connections when one fails', async () => {
202
+ builder = await NetworkTopologies.fiveNode();
203
+ const leader = builder.getNode('o://leader');
204
+ const child1 = builder.getNode('o://child1');
205
+ const child2 = builder.getNode('o://child2');
206
+ // Establish connections to both children
207
+ await leader.use(child1.address, {
208
+ method: 'echo',
209
+ params: { message: 'child1' },
210
+ });
211
+ await leader.use(child2.address, {
212
+ method: 'echo',
213
+ params: { message: 'child2' },
214
+ });
215
+ // Stop child1
216
+ await builder.stopNode('o://child1');
217
+ // Connection to child2 should still work
218
+ const response = await leader.use(child2.address, {
219
+ method: 'echo',
220
+ params: { message: 'child2-after' },
221
+ });
222
+ expect(response.result.success).to.be.true;
223
+ expect(response.result.data.message).to.equal('child2-after');
224
+ });
225
+ });
226
+ describe('Multi-node Connection Management', () => {
227
+ it('should manage connections in complex topology', async () => {
228
+ builder = await NetworkTopologies.complex();
229
+ const leader = builder.getNode('o://leader');
230
+ const spy = createConnectionSpy(leader);
231
+ spy.start();
232
+ // Make requests to various nodes
233
+ for (let i = 1; i <= 3; i++) {
234
+ const parent = builder.getNode(`o://parent${i}`);
235
+ await leader.use(parent.address, {
236
+ method: 'echo',
237
+ params: { message: `parent${i}` },
238
+ });
239
+ }
240
+ const summary = spy.getSummary();
241
+ // Should have connections to parents
242
+ expect(summary.currentConnections).to.be.greaterThan(0);
243
+ expect(summary.currentConnections).to.be.lessThan(10);
244
+ spy.stop();
245
+ });
246
+ });
247
+ describe('Connection Metadata', () => {
248
+ it('should track connection creation time', async () => {
249
+ builder = await NetworkTopologies.twoNode();
250
+ const leader = builder.getNode('o://leader');
251
+ const child = builder.getNode('o://child');
252
+ const spy = createConnectionSpy(leader);
253
+ spy.start();
254
+ const beforeTime = Date.now();
255
+ await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
256
+ method: 'echo',
257
+ params: { message: 'test' },
258
+ });
259
+ const afterTime = Date.now();
260
+ const stats = spy.getConnectionStats();
261
+ if (stats.length > 0) {
262
+ expect(stats[0].created).to.be.at.least(beforeTime);
263
+ expect(stats[0].created).to.be.at.most(afterTime);
264
+ }
265
+ spy.stop();
266
+ });
267
+ it('should track remote peer information', async () => {
268
+ builder = await NetworkTopologies.twoNode();
269
+ const leader = builder.getNode('o://leader');
270
+ const child = builder.getNode('o://child');
271
+ const spy = createConnectionSpy(leader);
272
+ spy.start();
273
+ await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
274
+ method: 'echo',
275
+ params: { message: 'test' },
276
+ });
277
+ const stats = spy.getConnectionStats();
278
+ if (stats.length > 0) {
279
+ const connection = stats[0];
280
+ expect(connection.peerId).to.be.a('string');
281
+ expect(connection.peerId.length).to.be.greaterThan(0);
282
+ expect(connection.remoteAddr).to.be.a('string');
283
+ expect(connection.remoteAddr.length).to.be.greaterThan(0);
284
+ }
285
+ spy.stop();
286
+ });
287
+ });
288
+ describe('Connection Gating', () => {
289
+ it('should enforce connection gating rules', async () => {
290
+ builder = await NetworkTopologies.twoNode();
291
+ const leader = builder.getNode('o://leader');
292
+ const child = builder.getNode('o://child');
293
+ // Parent-child connections should be allowed
294
+ const response = await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
295
+ method: 'echo',
296
+ params: { message: 'test' },
297
+ });
298
+ expect(response.result.success).to.be.true;
299
+ });
300
+ it('should allow connections within hierarchy', async () => {
301
+ builder = await NetworkTopologies.threeNode();
302
+ const leader = builder.getNode('o://leader');
303
+ const parent = builder.getNode('o://parent');
304
+ const child = builder.getNode('o://child');
305
+ // All connections in hierarchy should work
306
+ const response1 = await leader.use(parent.address, {
307
+ method: 'echo',
308
+ params: { message: 'leader-to-parent' },
309
+ });
310
+ const response2 = await parent.use(child.address, {
311
+ method: 'echo',
312
+ params: { message: 'parent-to-child' },
313
+ });
314
+ const response3 = await child.use(parent.address, {
315
+ method: 'echo',
316
+ params: { message: 'child-to-parent' },
317
+ });
318
+ expect(response1.result.success).to.be.true;
319
+ expect(response2.result.success).to.be.true;
320
+ expect(response3.result.success).to.be.true;
321
+ });
322
+ });
323
+ describe('Connection Cleanup', () => {
324
+ it('should clean up connections on node stop', async () => {
325
+ builder = await NetworkTopologies.twoNode();
326
+ const leader = builder.getNode('o://leader');
327
+ const child = builder.getNode('o://child');
328
+ // Establish connection
329
+ await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
330
+ method: 'echo',
331
+ params: { message: 'test' },
332
+ });
333
+ const spy = createConnectionSpy(leader);
334
+ spy.start();
335
+ const beforeConnections = spy.getSummary().currentConnections;
336
+ expect(beforeConnections).to.be.greaterThan(0);
337
+ // Stop child
338
+ await builder.stopNode('o://child');
339
+ // Wait for cleanup
340
+ await new Promise((resolve) => setTimeout(resolve, 100));
341
+ // Note: Connection may still exist until cleanup cycle runs
342
+ // This test verifies stop mechanism doesn't throw errors
343
+ spy.stop();
344
+ });
345
+ it('should handle cleanup of multiple connections', async () => {
346
+ builder = await NetworkTopologies.fiveNode();
347
+ const leader = builder.getNode('o://leader');
348
+ // Establish connections
349
+ const child1 = builder.getNode('o://child1');
350
+ const child2 = builder.getNode('o://child2');
351
+ await leader.use(child1.address, {
352
+ method: 'echo',
353
+ params: { message: 'child1' },
354
+ });
355
+ await leader.use(child2.address, {
356
+ method: 'echo',
357
+ params: { message: 'child2' },
358
+ });
359
+ // Stop all children
360
+ await builder.stopNode('o://child1');
361
+ await builder.stopNode('o://child2');
362
+ // Leader should remain operational
363
+ const response = await leader.use(leader.address, {
364
+ method: 'get_info',
365
+ params: {},
366
+ });
367
+ expect(response.result.success).to.be.true;
368
+ });
369
+ });
370
+ });
@@ -0,0 +1,124 @@
1
+ import { oNode } from '../../src/o-node.js';
2
+ import { oNodeTool } from '../../src/o-node.tool.js';
3
+ import type { Connection, Stream } from '@libp2p/interface';
4
+ /**
5
+ * Connection event captured by the spy
6
+ */
7
+ export interface ConnectionEvent {
8
+ type: 'peer:connect' | 'peer:disconnect' | 'connection:open' | 'connection:close' | 'stream:open' | 'stream:close';
9
+ timestamp: number;
10
+ peerId?: string;
11
+ remoteAddr?: string;
12
+ protocol?: string;
13
+ streamId?: string;
14
+ metadata?: Record<string, any>;
15
+ }
16
+ /**
17
+ * Stream statistics
18
+ */
19
+ export interface StreamStats {
20
+ protocol: string;
21
+ status: string;
22
+ writeStatus?: string;
23
+ readStatus?: string;
24
+ created: number;
25
+ closed?: number;
26
+ }
27
+ /**
28
+ * Connection statistics
29
+ */
30
+ export interface ConnectionStats {
31
+ peerId: string;
32
+ status: string;
33
+ remoteAddr: string;
34
+ streams: StreamStats[];
35
+ created: number;
36
+ closed?: number;
37
+ }
38
+ /**
39
+ * Spy that monitors libp2p connections and streams for testing
40
+ */
41
+ export declare class ConnectionSpy {
42
+ private events;
43
+ private node;
44
+ private listeners;
45
+ private isMonitoring;
46
+ constructor(node: oNode | oNodeTool);
47
+ /**
48
+ * Start monitoring connection events
49
+ */
50
+ start(): void;
51
+ /**
52
+ * Stop monitoring
53
+ */
54
+ stop(): void;
55
+ /**
56
+ * Clear all captured events
57
+ */
58
+ clear(): void;
59
+ /**
60
+ * Get all captured events
61
+ */
62
+ getEvents(): ConnectionEvent[];
63
+ /**
64
+ * Get events of a specific type
65
+ */
66
+ getEventsByType(type: ConnectionEvent['type']): ConnectionEvent[];
67
+ /**
68
+ * Get events for a specific peer
69
+ */
70
+ getEventsForPeer(peerId: string): ConnectionEvent[];
71
+ /**
72
+ * Get current connection statistics
73
+ */
74
+ getConnectionStats(): ConnectionStats[];
75
+ /**
76
+ * Get total stream count across all connections
77
+ */
78
+ getTotalStreamCount(): number;
79
+ /**
80
+ * Get stream count for a specific protocol
81
+ */
82
+ getStreamCountByProtocol(protocol: string): number;
83
+ /**
84
+ * Check if a connection exists to a peer
85
+ */
86
+ hasConnectionToPeer(peerId: string): boolean;
87
+ /**
88
+ * Get connection to a specific peer
89
+ */
90
+ getConnectionToPeer(peerId: string): Connection | undefined;
91
+ /**
92
+ * Get all streams for a specific peer
93
+ */
94
+ getStreamsForPeer(peerId: string): Stream[];
95
+ /**
96
+ * Wait for a specific event to occur
97
+ */
98
+ waitForEvent(type: ConnectionEvent['type'], timeoutMs?: number): Promise<ConnectionEvent>;
99
+ /**
100
+ * Wait for connection to a specific peer
101
+ */
102
+ waitForConnectionToPeer(peerId: string, timeoutMs?: number): Promise<Connection>;
103
+ /**
104
+ * Wait for stream count to reach expected value
105
+ */
106
+ waitForStreamCount(expectedCount: number, timeoutMs?: number): Promise<void>;
107
+ /**
108
+ * Get summary of connection activity
109
+ */
110
+ getSummary(): {
111
+ totalEvents: number;
112
+ connectionOpens: number;
113
+ connectionCloses: number;
114
+ peerConnects: number;
115
+ peerDisconnects: number;
116
+ currentConnections: number;
117
+ currentStreams: number;
118
+ };
119
+ }
120
+ /**
121
+ * Create a connection spy for a node
122
+ */
123
+ export declare function createConnectionSpy(node: oNode | oNodeTool): ConnectionSpy;
124
+ //# sourceMappingURL=connection-spy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-spy.d.ts","sourceRoot":"","sources":["../../../test/helpers/connection-spy.ts"],"names":[],"mappings":"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"}