@olane/o-node 0.7.12-alpha.55 → 0.7.12-alpha.57
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/dist/src/connection/o-node-connection.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.js +2 -1
- package/dist/src/connection/o-node-connection.manager.d.ts +2 -2
- package/dist/src/connection/o-node-connection.manager.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.manager.js +1 -0
- package/dist/src/connection/stream-handler.d.ts +3 -1
- package/dist/src/connection/stream-handler.d.ts.map +1 -1
- package/dist/src/connection/stream-handler.js +15 -3
- package/package.json +6 -6
- package/dist/src/connection/stream-handler.spec.d.ts +0 -2
- package/dist/src/connection/stream-handler.spec.d.ts.map +0 -1
- package/dist/src/connection/stream-handler.spec.js +0 -309
- package/dist/test/o-node.spec.d.ts +0 -2
- package/dist/test/o-node.spec.d.ts.map +0 -1
- package/dist/test/o-node.spec.js +0 -20
- package/dist/test/search-resolver.spec.d.ts +0 -2
- package/dist/test/search-resolver.spec.d.ts.map +0 -1
- package/dist/test/search-resolver.spec.js +0 -693
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node-connection.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAEL,WAAW,EAGX,QAAQ,EACR,SAAS,EACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGpD,qBAAa,eAAgB,SAAQ,WAAW;IAIlC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,qBAAqB;IAHrD,aAAa,EAAE,UAAU,CAAC;IACjC,SAAS,CAAC,aAAa,EAAE,aAAa,CAAC;gBAER,MAAM,EAAE,qBAAqB;IAO5D,wBAAwB;IAYxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;IAmBlB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;IAkBpC,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"o-node-connection.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAEL,WAAW,EAGX,QAAQ,EACR,SAAS,EACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGpD,qBAAa,eAAgB,SAAQ,WAAW;IAIlC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,qBAAqB;IAHrD,aAAa,EAAE,UAAU,CAAC;IACjC,SAAS,CAAC,aAAa,EAAE,aAAa,CAAC;gBAER,MAAM,EAAE,qBAAqB;IAO5D,wBAAwB;IAYxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;IAmBlB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;IAkBpC,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IAwC/C,YAAY,CAAC,MAAM,EAAE,MAAM;IAQ3B,KAAK,CAAC,KAAK,EAAE,KAAK;IAMlB,KAAK;CAKZ"}
|
|
@@ -55,7 +55,8 @@ export class oNodeConnection extends oConnection {
|
|
|
55
55
|
const data = new TextEncoder().encode(request.toString());
|
|
56
56
|
await this.streamHandler.send(stream, data, streamConfig);
|
|
57
57
|
// Handle response using StreamHandler
|
|
58
|
-
|
|
58
|
+
// Pass request handler if configured to enable bidirectional stream processing
|
|
59
|
+
const response = await this.streamHandler.handleOutgoingStream(stream, this.emitter, streamConfig, this.config.requestHandler);
|
|
59
60
|
// Handle cleanup of the stream
|
|
60
61
|
await this.postTransmit(stream);
|
|
61
62
|
return response;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { oAddress, oConnectionConfig, oConnectionManager } from '@olane/o-core';
|
|
2
|
-
import { Connection } from '@olane/o-config';
|
|
2
|
+
import { Libp2p, Connection } from '@olane/o-config';
|
|
3
3
|
import { oNodeConnectionManagerConfig } from './interfaces/o-node-connection-manager.config.js';
|
|
4
4
|
import { oNodeConnection } from './o-node-connection.js';
|
|
5
5
|
export declare class oNodeConnectionManager extends oConnectionManager {
|
|
6
6
|
readonly config: oNodeConnectionManagerConfig;
|
|
7
|
-
|
|
7
|
+
protected p2pNode: Libp2p;
|
|
8
8
|
private defaultReadTimeoutMs?;
|
|
9
9
|
private defaultDrainTimeoutMs?;
|
|
10
10
|
constructor(config: oNodeConnectionManagerConfig);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node-connection.manager.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,
|
|
1
|
+
{"version":3,"file":"o-node-connection.manager.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,MAAM,EAAE,UAAU,EAAU,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,4BAA4B,EAAE,MAAM,kDAAkD,CAAC;AAEhG,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,qBAAa,sBAAuB,SAAQ,kBAAkB;IAKhD,QAAQ,CAAC,MAAM,EAAE,4BAA4B;IAJzD,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,oBAAoB,CAAC,CAAS;IACtC,OAAO,CAAC,qBAAqB,CAAC,CAAS;gBAElB,MAAM,EAAE,4BAA4B;IAOnD,qBAAqB,CACzB,cAAc,EAAE,QAAQ,EACxB,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,UAAU,CAAC;IA4BtB;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IA8BlE;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO;IA4BpC;;;;OAIG;IACH,yBAAyB,CAAC,OAAO,EAAE,QAAQ,GAAG,UAAU,GAAG,IAAI;CA0BhE"}
|
|
@@ -42,6 +42,7 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
42
42
|
isStream: config.isStream ?? false,
|
|
43
43
|
abortSignal: config.abortSignal,
|
|
44
44
|
runOnLimitedConnection: this.config.runOnLimitedConnection ?? false,
|
|
45
|
+
requestHandler: config.requestHandler ?? undefined,
|
|
45
46
|
});
|
|
46
47
|
return connection;
|
|
47
48
|
}
|
|
@@ -75,13 +75,15 @@ export declare class StreamHandler {
|
|
|
75
75
|
/**
|
|
76
76
|
* Handles an outgoing stream on the client side
|
|
77
77
|
* Listens for response messages and emits them via the event emitter
|
|
78
|
+
* If requestHandler is provided, also processes incoming router requests
|
|
78
79
|
*
|
|
79
80
|
* @param stream - The outgoing stream
|
|
80
81
|
* @param emitter - Event emitter for chunk events
|
|
81
82
|
* @param config - Configuration including abort signal
|
|
83
|
+
* @param requestHandler - Optional handler for processing router requests received on this stream
|
|
82
84
|
* @returns Promise that resolves with the final response
|
|
83
85
|
*/
|
|
84
|
-
handleOutgoingStream(stream: Stream, emitter: EventEmitter, config?: StreamHandlerConfig): Promise<oResponse>;
|
|
86
|
+
handleOutgoingStream(stream: Stream, emitter: EventEmitter, config?: StreamHandlerConfig, requestHandler?: (request: oRequest, stream: Stream) => Promise<RunResult>): Promise<oResponse>;
|
|
85
87
|
/**
|
|
86
88
|
* Forwards a request to the next hop and relays response chunks back
|
|
87
89
|
* This implements the middleware/proxy pattern for intermediate nodes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-handler.d.ts","sourceRoot":"","sources":["../../../src/connection/stream-handler.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EACL,QAAQ,EACR,SAAS,EAIT,MAAM,EAEP,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGtE;;;;;;;GAOG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,CAAC,EAAE,MAAM;IAI3B;;;OAGG;IACH,SAAS,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO;IAIhC;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO;IAIjC;;OAEG;IACG,aAAa,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAI7C;;;;;;OAMG;IACG,iBAAiB,CACrB,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,mBAAwB,GAC/B,OAAO,CAAC,MAAM,CAAC;IAyDlB;;;;;;OAMG;IACG,IAAI,CACR,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,UAAU,EAChB,MAAM,GAAE,mBAAwB,GAC/B,OAAO,CAAC,IAAI,CAAC;IAiBhB;;;;;OAKG;IACG,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB5E;;;;;;;;OAQG;IACG,oBAAoB,CACxB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,GACtE,OAAO,CAAC,IAAI,CAAC;IA2ChB;;;;;;OAMG;YACW,oBAAoB;
|
|
1
|
+
{"version":3,"file":"stream-handler.d.ts","sourceRoot":"","sources":["../../../src/connection/stream-handler.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EACL,QAAQ,EACR,SAAS,EAIT,MAAM,EAEP,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGtE;;;;;;;GAOG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,CAAC,EAAE,MAAM;IAI3B;;;OAGG;IACH,SAAS,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO;IAIhC;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO;IAIjC;;OAEG;IACG,aAAa,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAI7C;;;;;;OAMG;IACG,iBAAiB,CACrB,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,mBAAwB,GAC/B,OAAO,CAAC,MAAM,CAAC;IAyDlB;;;;;;OAMG;IACG,IAAI,CACR,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,UAAU,EAChB,MAAM,GAAE,mBAAwB,GAC/B,OAAO,CAAC,IAAI,CAAC;IAiBhB;;;;;OAKG;IACG,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB5E;;;;;;;;OAQG;IACG,oBAAoB,CACxB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,GACtE,OAAO,CAAC,IAAI,CAAC;IA2ChB;;;;;;OAMG;YACW,oBAAoB;IA4BlC;;;;;;;;;;OAUG;IACG,oBAAoB,CACxB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,YAAY,EACrB,MAAM,GAAE,mBAAwB,EAChC,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,GACzE,OAAO,CAAC,SAAS,CAAC;IAsGrB;;;;;;;OAOG;IACG,cAAc,CAClB,OAAO,EAAE,cAAc,EACvB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,GAChD,OAAO,CAAC,IAAI,CAAC;CAyBjB"}
|
|
@@ -54,7 +54,7 @@ export class StreamHandler {
|
|
|
54
54
|
this.logger.debug('Stream re-use option:', stream.protocol, stream.status, stream.direction, stream.remoteWriteStatus, stream.writeStatus, stream.remoteReadStatus, stream.readStatus);
|
|
55
55
|
});
|
|
56
56
|
const existingStream = connection.streams.find((stream) => stream.status === 'open' &&
|
|
57
|
-
stream.protocol
|
|
57
|
+
stream.protocol === protocol &&
|
|
58
58
|
stream.writeStatus === 'writable' &&
|
|
59
59
|
stream.remoteReadStatus === 'readable');
|
|
60
60
|
if (existingStream) {
|
|
@@ -171,11 +171,14 @@ export class StreamHandler {
|
|
|
171
171
|
const request = new oRequest(message);
|
|
172
172
|
const responseBuilder = ResponseBuilder.create();
|
|
173
173
|
try {
|
|
174
|
+
this.logger.debug(`Processing request on stream: method=${request.method}, id=${request.id}`);
|
|
174
175
|
const result = await toolExecutor(request, stream);
|
|
175
176
|
const response = await responseBuilder.build(request, result, null);
|
|
176
177
|
await CoreUtils.sendResponse(response, stream);
|
|
178
|
+
this.logger.debug(`Successfully processed request: method=${request.method}, id=${request.id}`);
|
|
177
179
|
}
|
|
178
180
|
catch (error) {
|
|
181
|
+
this.logger.error(`Error processing request: method=${request.method}, id=${request.id}`, error);
|
|
179
182
|
const errorResponse = await responseBuilder.buildError(request, error);
|
|
180
183
|
await CoreUtils.sendResponse(errorResponse, stream);
|
|
181
184
|
}
|
|
@@ -183,13 +186,15 @@ export class StreamHandler {
|
|
|
183
186
|
/**
|
|
184
187
|
* Handles an outgoing stream on the client side
|
|
185
188
|
* Listens for response messages and emits them via the event emitter
|
|
189
|
+
* If requestHandler is provided, also processes incoming router requests
|
|
186
190
|
*
|
|
187
191
|
* @param stream - The outgoing stream
|
|
188
192
|
* @param emitter - Event emitter for chunk events
|
|
189
193
|
* @param config - Configuration including abort signal
|
|
194
|
+
* @param requestHandler - Optional handler for processing router requests received on this stream
|
|
190
195
|
* @returns Promise that resolves with the final response
|
|
191
196
|
*/
|
|
192
|
-
async handleOutgoingStream(stream, emitter, config = {}) {
|
|
197
|
+
async handleOutgoingStream(stream, emitter, config = {}, requestHandler) {
|
|
193
198
|
return new Promise((resolve, reject) => {
|
|
194
199
|
let lastResponse;
|
|
195
200
|
const messageHandler = async (event) => {
|
|
@@ -218,7 +223,14 @@ export class StreamHandler {
|
|
|
218
223
|
}
|
|
219
224
|
}
|
|
220
225
|
else if (this.isRequest(message)) {
|
|
221
|
-
|
|
226
|
+
// Process incoming router requests if handler is provided
|
|
227
|
+
if (requestHandler) {
|
|
228
|
+
this.logger.debug('Received router request on client-side stream, processing...', message);
|
|
229
|
+
await this.handleRequestMessage(message, stream, requestHandler);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
this.logger.warn('Received request message on client-side stream, ignoring (no handler)', message);
|
|
233
|
+
}
|
|
222
234
|
}
|
|
223
235
|
else {
|
|
224
236
|
this.logger.warn('Received unknown message type', message);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@olane/o-node",
|
|
3
|
-
"version": "0.7.12-alpha.
|
|
3
|
+
"version": "0.7.12-alpha.57",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -54,12 +54,12 @@
|
|
|
54
54
|
"typescript": "5.4.5"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@olane/o-config": "0.7.12-alpha.
|
|
58
|
-
"@olane/o-core": "0.7.12-alpha.
|
|
59
|
-
"@olane/o-protocol": "0.7.12-alpha.
|
|
60
|
-
"@olane/o-tool": "0.7.12-alpha.
|
|
57
|
+
"@olane/o-config": "0.7.12-alpha.57",
|
|
58
|
+
"@olane/o-core": "0.7.12-alpha.57",
|
|
59
|
+
"@olane/o-protocol": "0.7.12-alpha.57",
|
|
60
|
+
"@olane/o-tool": "0.7.12-alpha.57",
|
|
61
61
|
"debug": "^4.4.1",
|
|
62
62
|
"dotenv": "^16.5.0"
|
|
63
63
|
},
|
|
64
|
-
"gitHead": "
|
|
64
|
+
"gitHead": "71a484e394405a5398d0955dabac179465384e89"
|
|
65
65
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"stream-handler.spec.d.ts","sourceRoot":"","sources":["../../../src/connection/stream-handler.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { StreamHandler } from './stream-handler.js';
|
|
3
|
-
import { EventEmitter } from 'events';
|
|
4
|
-
import { oError, oErrorCodes } from '@o/o-core/src/error/o-error';
|
|
5
|
-
// Mock Stream interface
|
|
6
|
-
const createMockStream = (status = 'open') => {
|
|
7
|
-
const listeners = new Map();
|
|
8
|
-
return {
|
|
9
|
-
status,
|
|
10
|
-
send: vi.fn().mockReturnValue(true),
|
|
11
|
-
close: vi.fn().mockResolvedValue(undefined),
|
|
12
|
-
abort: vi.fn().mockResolvedValue(undefined),
|
|
13
|
-
onDrain: vi.fn().mockResolvedValue(undefined),
|
|
14
|
-
addEventListener: vi.fn((event, handler) => {
|
|
15
|
-
if (!listeners.has(event)) {
|
|
16
|
-
listeners.set(event, []);
|
|
17
|
-
}
|
|
18
|
-
listeners.get(event)?.push(handler);
|
|
19
|
-
}),
|
|
20
|
-
removeEventListener: vi.fn((event, handler) => {
|
|
21
|
-
const handlers = listeners.get(event);
|
|
22
|
-
if (handlers) {
|
|
23
|
-
const index = handlers.indexOf(handler);
|
|
24
|
-
if (index > -1) {
|
|
25
|
-
handlers.splice(index, 1);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}),
|
|
29
|
-
// Helper to trigger events in tests
|
|
30
|
-
_triggerEvent: (event, data) => {
|
|
31
|
-
listeners.get(event)?.forEach((handler) => handler(data));
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
};
|
|
35
|
-
// Mock Connection interface
|
|
36
|
-
const createMockConnection = (status = 'open') => {
|
|
37
|
-
return {
|
|
38
|
-
status,
|
|
39
|
-
streams: [],
|
|
40
|
-
newStream: vi.fn().mockImplementation(() => createMockStream()),
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
|
-
describe('StreamHandler', () => {
|
|
44
|
-
let streamHandler;
|
|
45
|
-
beforeEach(() => {
|
|
46
|
-
streamHandler = new StreamHandler();
|
|
47
|
-
});
|
|
48
|
-
describe('Message Type Detection', () => {
|
|
49
|
-
it('should identify a request message', () => {
|
|
50
|
-
const message = {
|
|
51
|
-
jsonrpc: '2.0',
|
|
52
|
-
id: '1',
|
|
53
|
-
method: 'test_method',
|
|
54
|
-
params: {},
|
|
55
|
-
};
|
|
56
|
-
expect(streamHandler.isRequest(message)).toBe(true);
|
|
57
|
-
expect(streamHandler.isResponse(message)).toBe(false);
|
|
58
|
-
});
|
|
59
|
-
it('should identify a response message', () => {
|
|
60
|
-
const message = {
|
|
61
|
-
jsonrpc: '2.0',
|
|
62
|
-
id: '1',
|
|
63
|
-
result: { success: true },
|
|
64
|
-
};
|
|
65
|
-
expect(streamHandler.isResponse(message)).toBe(true);
|
|
66
|
-
expect(streamHandler.isRequest(message)).toBe(false);
|
|
67
|
-
});
|
|
68
|
-
it('should not identify malformed message as request or response', () => {
|
|
69
|
-
const message = {
|
|
70
|
-
jsonrpc: '2.0',
|
|
71
|
-
id: '1',
|
|
72
|
-
};
|
|
73
|
-
expect(streamHandler.isRequest(message)).toBe(false);
|
|
74
|
-
expect(streamHandler.isResponse(message)).toBe(false);
|
|
75
|
-
});
|
|
76
|
-
it('should handle message with both method and result', () => {
|
|
77
|
-
const message = {
|
|
78
|
-
jsonrpc: '2.0',
|
|
79
|
-
id: '1',
|
|
80
|
-
method: 'test',
|
|
81
|
-
result: {},
|
|
82
|
-
};
|
|
83
|
-
// Should identify as request since method is present
|
|
84
|
-
expect(streamHandler.isRequest(message)).toBe(true);
|
|
85
|
-
expect(streamHandler.isResponse(message)).toBe(false);
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
describe('Stream Lifecycle - getOrCreateStream', () => {
|
|
89
|
-
it('should create a new stream with none reuse policy', async () => {
|
|
90
|
-
const connection = createMockConnection();
|
|
91
|
-
const protocol = '/test/1.0.0';
|
|
92
|
-
const stream = await streamHandler.getOrCreateStream(connection, protocol, {
|
|
93
|
-
reusePolicy: 'none',
|
|
94
|
-
});
|
|
95
|
-
expect(stream).toBeDefined();
|
|
96
|
-
expect(connection.newStream).toHaveBeenCalledWith(protocol, expect.any(Object));
|
|
97
|
-
});
|
|
98
|
-
it('should reuse existing stream with reuse policy', async () => {
|
|
99
|
-
const existingStream = createMockStream();
|
|
100
|
-
const connection = createMockConnection();
|
|
101
|
-
connection.streams = [existingStream];
|
|
102
|
-
const stream = await streamHandler.getOrCreateStream(connection, '/test/1.0.0', { reusePolicy: 'reuse' });
|
|
103
|
-
expect(stream).toBe(existingStream);
|
|
104
|
-
expect(connection.newStream).not.toHaveBeenCalled();
|
|
105
|
-
});
|
|
106
|
-
it('should create new stream when no open stream exists with reuse policy', async () => {
|
|
107
|
-
const closedStream = createMockStream('closed');
|
|
108
|
-
const connection = createMockConnection();
|
|
109
|
-
connection.streams = [closedStream];
|
|
110
|
-
const stream = await streamHandler.getOrCreateStream(connection, '/test/1.0.0', { reusePolicy: 'reuse' });
|
|
111
|
-
expect(stream).not.toBe(closedStream);
|
|
112
|
-
expect(connection.newStream).toHaveBeenCalled();
|
|
113
|
-
});
|
|
114
|
-
it('should throw error when connection is not open', async () => {
|
|
115
|
-
const connection = createMockConnection('closed');
|
|
116
|
-
await expect(streamHandler.getOrCreateStream(connection, '/test/1.0.0')).rejects.toThrow();
|
|
117
|
-
});
|
|
118
|
-
it('should pass configuration options to newStream', async () => {
|
|
119
|
-
const connection = createMockConnection();
|
|
120
|
-
const signal = new AbortController().signal;
|
|
121
|
-
await streamHandler.getOrCreateStream(connection, '/test/1.0.0', {
|
|
122
|
-
signal,
|
|
123
|
-
maxOutboundStreams: 500,
|
|
124
|
-
runOnLimitedConnection: true,
|
|
125
|
-
});
|
|
126
|
-
expect(connection.newStream).toHaveBeenCalledWith('/test/1.0.0', {
|
|
127
|
-
signal,
|
|
128
|
-
maxOutboundStreams: 500,
|
|
129
|
-
runOnLimitedConnection: true,
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
describe('Stream Lifecycle - send', () => {
|
|
134
|
-
it('should send data without backpressure', async () => {
|
|
135
|
-
const stream = createMockStream();
|
|
136
|
-
stream.send = vi.fn().mockReturnValue(true);
|
|
137
|
-
const data = new TextEncoder().encode('test data');
|
|
138
|
-
await streamHandler.send(stream, data);
|
|
139
|
-
expect(stream.send).toHaveBeenCalledWith(data);
|
|
140
|
-
expect(stream.onDrain).not.toHaveBeenCalled();
|
|
141
|
-
});
|
|
142
|
-
it('should wait for drain when backpressure occurs', async () => {
|
|
143
|
-
const stream = createMockStream();
|
|
144
|
-
stream.send = vi.fn().mockReturnValue(false);
|
|
145
|
-
const data = new TextEncoder().encode('test data');
|
|
146
|
-
await streamHandler.send(stream, data, { drainTimeoutMs: 5000 });
|
|
147
|
-
expect(stream.send).toHaveBeenCalledWith(data);
|
|
148
|
-
expect(stream.onDrain).toHaveBeenCalledWith({
|
|
149
|
-
signal: expect.any(AbortSignal),
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
describe('Stream Lifecycle - close', () => {
|
|
154
|
-
it('should close stream when reuse policy is none', async () => {
|
|
155
|
-
const stream = createMockStream();
|
|
156
|
-
await streamHandler.close(stream, { reusePolicy: 'none' });
|
|
157
|
-
expect(stream.close).toHaveBeenCalled();
|
|
158
|
-
});
|
|
159
|
-
it('should not close stream when reuse policy is reuse', async () => {
|
|
160
|
-
const stream = createMockStream();
|
|
161
|
-
await streamHandler.close(stream, { reusePolicy: 'reuse' });
|
|
162
|
-
expect(stream.close).not.toHaveBeenCalled();
|
|
163
|
-
});
|
|
164
|
-
it('should not close stream when status is not open', async () => {
|
|
165
|
-
const stream = createMockStream('closed');
|
|
166
|
-
await streamHandler.close(stream, { reusePolicy: 'none' });
|
|
167
|
-
expect(stream.close).not.toHaveBeenCalled();
|
|
168
|
-
});
|
|
169
|
-
it('should handle errors during close gracefully', async () => {
|
|
170
|
-
const stream = createMockStream();
|
|
171
|
-
stream.close = vi.fn().mockRejectedValue(new Error('Close failed'));
|
|
172
|
-
// Should not throw
|
|
173
|
-
await expect(streamHandler.close(stream)).resolves.toBeUndefined();
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
describe('Server-side - handleIncomingStream', () => {
|
|
177
|
-
it('should attach message listener immediately', async () => {
|
|
178
|
-
const stream = createMockStream();
|
|
179
|
-
const connection = createMockConnection();
|
|
180
|
-
const toolExecutor = vi.fn().mockResolvedValue({ success: true });
|
|
181
|
-
await streamHandler.handleIncomingStream(stream, connection, toolExecutor);
|
|
182
|
-
expect(stream.addEventListener).toHaveBeenCalledWith('message', expect.any(Function));
|
|
183
|
-
expect(stream.addEventListener).toHaveBeenCalledWith('close', expect.any(Function));
|
|
184
|
-
});
|
|
185
|
-
it('should execute tool when request message is received', async () => {
|
|
186
|
-
const stream = createMockStream();
|
|
187
|
-
const connection = createMockConnection();
|
|
188
|
-
const toolExecutor = vi.fn().mockResolvedValue({ success: true });
|
|
189
|
-
await streamHandler.handleIncomingStream(stream, connection, toolExecutor);
|
|
190
|
-
// Simulate receiving a request
|
|
191
|
-
const requestData = {
|
|
192
|
-
data: new TextEncoder().encode(JSON.stringify({
|
|
193
|
-
jsonrpc: '2.0',
|
|
194
|
-
id: '1',
|
|
195
|
-
method: 'test_method',
|
|
196
|
-
params: {},
|
|
197
|
-
})),
|
|
198
|
-
};
|
|
199
|
-
stream._triggerEvent('message', requestData);
|
|
200
|
-
// Wait for async handling
|
|
201
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
202
|
-
expect(toolExecutor).toHaveBeenCalled();
|
|
203
|
-
});
|
|
204
|
-
it('should send error response when tool execution fails', async () => {
|
|
205
|
-
const stream = createMockStream();
|
|
206
|
-
const connection = createMockConnection();
|
|
207
|
-
const toolExecutor = vi.fn().mockRejectedValue(new oError(oErrorCodes.INTERNAL_ERROR, 'Tool execution failed'));
|
|
208
|
-
await streamHandler.handleIncomingStream(stream, connection, toolExecutor);
|
|
209
|
-
const requestData = {
|
|
210
|
-
data: new TextEncoder().encode(JSON.stringify({
|
|
211
|
-
jsonrpc: '2.0',
|
|
212
|
-
id: '1',
|
|
213
|
-
method: 'test_method',
|
|
214
|
-
params: {},
|
|
215
|
-
})),
|
|
216
|
-
};
|
|
217
|
-
stream._triggerEvent('message', requestData);
|
|
218
|
-
// Wait for async handling
|
|
219
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
220
|
-
expect(stream.send).toHaveBeenCalled();
|
|
221
|
-
});
|
|
222
|
-
it('should clean up listeners on stream close', async () => {
|
|
223
|
-
const stream = createMockStream();
|
|
224
|
-
const connection = createMockConnection();
|
|
225
|
-
const toolExecutor = vi.fn();
|
|
226
|
-
await streamHandler.handleIncomingStream(stream, connection, toolExecutor);
|
|
227
|
-
stream._triggerEvent('close', {});
|
|
228
|
-
expect(stream.removeEventListener).toHaveBeenCalledWith('message', expect.any(Function));
|
|
229
|
-
expect(stream.removeEventListener).toHaveBeenCalledWith('close', expect.any(Function));
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
describe('Client-side - handleOutgoingStream', () => {
|
|
233
|
-
it('should resolve with response when final chunk received', async () => {
|
|
234
|
-
const stream = createMockStream();
|
|
235
|
-
const emitter = new EventEmitter();
|
|
236
|
-
const responsePromise = streamHandler.handleOutgoingStream(stream, emitter);
|
|
237
|
-
// Simulate receiving a final response
|
|
238
|
-
const responseData = {
|
|
239
|
-
data: new TextEncoder().encode(JSON.stringify({
|
|
240
|
-
jsonrpc: '2.0',
|
|
241
|
-
id: '1',
|
|
242
|
-
result: { _last: true, success: true },
|
|
243
|
-
})),
|
|
244
|
-
};
|
|
245
|
-
stream._triggerEvent('message', responseData);
|
|
246
|
-
const response = await responsePromise;
|
|
247
|
-
expect(response).toBeDefined();
|
|
248
|
-
expect(response.result._last).toBe(true);
|
|
249
|
-
});
|
|
250
|
-
it('should emit chunks for streaming responses', async () => {
|
|
251
|
-
const stream = createMockStream();
|
|
252
|
-
const emitter = new EventEmitter();
|
|
253
|
-
const chunkListener = vi.fn();
|
|
254
|
-
emitter.on('chunk', chunkListener);
|
|
255
|
-
const responsePromise = streamHandler.handleOutgoingStream(stream, emitter);
|
|
256
|
-
// Send a chunk
|
|
257
|
-
const chunkData = {
|
|
258
|
-
data: new TextEncoder().encode(JSON.stringify({
|
|
259
|
-
jsonrpc: '2.0',
|
|
260
|
-
id: '1',
|
|
261
|
-
result: { _isStreaming: true, _last: false, data: 'chunk 1' },
|
|
262
|
-
})),
|
|
263
|
-
};
|
|
264
|
-
stream._triggerEvent('message', chunkData);
|
|
265
|
-
// Wait for async handling
|
|
266
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
267
|
-
expect(chunkListener).toHaveBeenCalled();
|
|
268
|
-
// Send final chunk
|
|
269
|
-
const finalData = {
|
|
270
|
-
data: new TextEncoder().encode(JSON.stringify({
|
|
271
|
-
jsonrpc: '2.0',
|
|
272
|
-
id: '1',
|
|
273
|
-
result: { _isStreaming: true, _last: true, data: 'final chunk' },
|
|
274
|
-
})),
|
|
275
|
-
};
|
|
276
|
-
stream._triggerEvent('message', finalData);
|
|
277
|
-
await responsePromise;
|
|
278
|
-
});
|
|
279
|
-
it('should reject when stream closes before response', async () => {
|
|
280
|
-
const stream = createMockStream();
|
|
281
|
-
const emitter = new EventEmitter();
|
|
282
|
-
const responsePromise = streamHandler.handleOutgoingStream(stream, emitter);
|
|
283
|
-
stream._triggerEvent('close', {});
|
|
284
|
-
await expect(responsePromise).rejects.toThrow();
|
|
285
|
-
});
|
|
286
|
-
it('should handle abort signal', async () => {
|
|
287
|
-
const stream = createMockStream();
|
|
288
|
-
const emitter = new EventEmitter();
|
|
289
|
-
const abortController = new AbortController();
|
|
290
|
-
const responsePromise = streamHandler.handleOutgoingStream(stream, emitter, {
|
|
291
|
-
signal: abortController.signal,
|
|
292
|
-
});
|
|
293
|
-
abortController.abort();
|
|
294
|
-
// Wait for abort to be processed
|
|
295
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
296
|
-
await expect(responsePromise).rejects.toThrow();
|
|
297
|
-
expect(stream.abort).toHaveBeenCalled();
|
|
298
|
-
});
|
|
299
|
-
});
|
|
300
|
-
describe('Message Decoding', () => {
|
|
301
|
-
it('should decode Uint8Array message', async () => {
|
|
302
|
-
const testData = { test: 'data' };
|
|
303
|
-
const encoded = new TextEncoder().encode(JSON.stringify(testData));
|
|
304
|
-
const event = { data: encoded };
|
|
305
|
-
const decoded = await streamHandler.decodeMessage(event);
|
|
306
|
-
expect(decoded).toEqual(testData);
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"o-node.spec.d.ts","sourceRoot":"","sources":["../../test/o-node.spec.ts"],"names":[],"mappings":""}
|
package/dist/test/o-node.spec.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { NodeState } from '@olane/o-core';
|
|
2
|
-
import { expect } from 'chai';
|
|
3
|
-
import { oNode } from '../src/index.js';
|
|
4
|
-
import { oNodeAddress } from '../src/router/o-node.address.js';
|
|
5
|
-
describe('in-process @memory', () => {
|
|
6
|
-
it('should be able to start a single node with no leader', async () => {
|
|
7
|
-
const node = new oNode({
|
|
8
|
-
address: new oNodeAddress('o://test'),
|
|
9
|
-
leader: null,
|
|
10
|
-
parent: null,
|
|
11
|
-
});
|
|
12
|
-
await node.start();
|
|
13
|
-
expect(node.state).to.equal(NodeState.RUNNING);
|
|
14
|
-
const transports = node.transports;
|
|
15
|
-
// expect(transports.length).to.equal(1);
|
|
16
|
-
// expect(transports[0].toString()).to.contain('/memory');
|
|
17
|
-
await node.stop();
|
|
18
|
-
expect(node.state).to.equal(NodeState.STOPPED);
|
|
19
|
-
});
|
|
20
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"search-resolver.spec.d.ts","sourceRoot":"","sources":["../../test/search-resolver.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1,693 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import { oSearchResolver } from '../src/router/resolvers/o-node.search-resolver.js';
|
|
3
|
-
import { oAddress, RestrictedAddresses, NodeState, TransportType, } from '@olane/o-core';
|
|
4
|
-
import { oNodeTransport } from '../src/router/o-node.transport.js';
|
|
5
|
-
import { oNodeAddress } from '../src/router/o-node.address.js';
|
|
6
|
-
import { oProtocolMethods } from '@olane/o-protocol';
|
|
7
|
-
describe('oSearchResolver', () => {
|
|
8
|
-
let resolver;
|
|
9
|
-
let mockNode;
|
|
10
|
-
const testAddress = new oNodeAddress('o://test-node');
|
|
11
|
-
// Helper to create a proper oRouterRequest
|
|
12
|
-
const createRouterRequest = () => ({
|
|
13
|
-
method: oProtocolMethods.ROUTE,
|
|
14
|
-
params: {
|
|
15
|
-
_connectionId: 'test-connection',
|
|
16
|
-
_requestMethod: 'test',
|
|
17
|
-
address: 'o://test',
|
|
18
|
-
payload: {},
|
|
19
|
-
},
|
|
20
|
-
jsonrpc: '2.0',
|
|
21
|
-
id: '1',
|
|
22
|
-
state: {},
|
|
23
|
-
connectionId: 'test-connection',
|
|
24
|
-
toJSON: () => ({}),
|
|
25
|
-
setState: () => { },
|
|
26
|
-
logger: {},
|
|
27
|
-
});
|
|
28
|
-
// Helper to create a proper oResponse
|
|
29
|
-
const createResponse = (result) => ({
|
|
30
|
-
jsonrpc: '2.0',
|
|
31
|
-
id: '1',
|
|
32
|
-
result,
|
|
33
|
-
});
|
|
34
|
-
beforeEach(() => {
|
|
35
|
-
resolver = new oSearchResolver(testAddress);
|
|
36
|
-
// Create mock node with necessary properties
|
|
37
|
-
mockNode = {
|
|
38
|
-
address: testAddress,
|
|
39
|
-
state: NodeState.RUNNING,
|
|
40
|
-
leader: new oNodeAddress(RestrictedAddresses.LEADER, [
|
|
41
|
-
new oNodeTransport('/ip4/127.0.0.1/tcp/4001'),
|
|
42
|
-
]),
|
|
43
|
-
hierarchyManager: {
|
|
44
|
-
getChild: (address) => null,
|
|
45
|
-
},
|
|
46
|
-
use: async (address, request) => {
|
|
47
|
-
// Mock registry search response
|
|
48
|
-
return createResponse({
|
|
49
|
-
data: [],
|
|
50
|
-
});
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
});
|
|
54
|
-
describe('Basic resolver configuration', () => {
|
|
55
|
-
it('should have custom transport for /search', () => {
|
|
56
|
-
const transports = resolver.customTransports;
|
|
57
|
-
expect(transports).to.have.lengthOf(1);
|
|
58
|
-
expect(transports[0].value).to.equal('/search');
|
|
59
|
-
});
|
|
60
|
-
it('should use registry as default search target', () => {
|
|
61
|
-
const registryAddress = resolver.getRegistryAddress();
|
|
62
|
-
expect(registryAddress.value).to.equal(RestrictedAddresses.REGISTRY);
|
|
63
|
-
});
|
|
64
|
-
it('should use "search" as default method', () => {
|
|
65
|
-
const method = resolver.getSearchMethod();
|
|
66
|
-
expect(method).to.equal('search');
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
describe('resolve() - Basic flow', () => {
|
|
70
|
-
it('should skip search if address already has transports', async () => {
|
|
71
|
-
const addressWithTransports = new oNodeAddress('o://test', [
|
|
72
|
-
new oNodeTransport('/ip4/127.0.0.1/tcp/5001'),
|
|
73
|
-
]);
|
|
74
|
-
const result = await resolver.resolve({
|
|
75
|
-
address: addressWithTransports,
|
|
76
|
-
targetAddress: addressWithTransports,
|
|
77
|
-
node: mockNode,
|
|
78
|
-
request: createRouterRequest(),
|
|
79
|
-
});
|
|
80
|
-
expect(result.nextHopAddress.value).to.equal('o://test');
|
|
81
|
-
expect(result.nextHopAddress.transports).to.have.lengthOf(1);
|
|
82
|
-
});
|
|
83
|
-
it('should return original address when no search results found', async () => {
|
|
84
|
-
const address = new oNodeAddress('o://unknown-service');
|
|
85
|
-
mockNode.use = async () => createResponse({ data: [] });
|
|
86
|
-
const result = await resolver.resolve({
|
|
87
|
-
address,
|
|
88
|
-
targetAddress: address,
|
|
89
|
-
node: mockNode,
|
|
90
|
-
request: createRouterRequest(),
|
|
91
|
-
});
|
|
92
|
-
expect(result.nextHopAddress.value).to.equal('o://unknown-service');
|
|
93
|
-
});
|
|
94
|
-
it('should resolve address using registry search results', async () => {
|
|
95
|
-
const address = new oNodeAddress('o://embeddings-text');
|
|
96
|
-
mockNode.use = async () => createResponse({
|
|
97
|
-
data: [
|
|
98
|
-
{
|
|
99
|
-
address: 'o://leader/services/embeddings',
|
|
100
|
-
staticAddress: 'o://embeddings-text',
|
|
101
|
-
transports: [
|
|
102
|
-
{
|
|
103
|
-
value: '/ip4/127.0.0.1/tcp/6001',
|
|
104
|
-
type: TransportType.LIBP2P,
|
|
105
|
-
},
|
|
106
|
-
],
|
|
107
|
-
},
|
|
108
|
-
],
|
|
109
|
-
});
|
|
110
|
-
const result = await resolver.resolve({
|
|
111
|
-
address,
|
|
112
|
-
targetAddress: address,
|
|
113
|
-
node: mockNode,
|
|
114
|
-
request: createRouterRequest(),
|
|
115
|
-
});
|
|
116
|
-
expect(result.targetAddress.value).to.equal('o://leader/services/embeddings');
|
|
117
|
-
expect(result.targetAddress.transports).to.have.lengthOf(1);
|
|
118
|
-
});
|
|
119
|
-
it('should preserve extra path parameters in resolved address', async () => {
|
|
120
|
-
const address = new oNodeAddress('o://embeddings-text/method/params');
|
|
121
|
-
mockNode.use = async () => createResponse({
|
|
122
|
-
data: [
|
|
123
|
-
{
|
|
124
|
-
address: 'o://leader/services/embeddings',
|
|
125
|
-
staticAddress: 'o://embeddings-text',
|
|
126
|
-
transports: [
|
|
127
|
-
{
|
|
128
|
-
value: '/ip4/127.0.0.1/tcp/6001',
|
|
129
|
-
type: TransportType.LIBP2P,
|
|
130
|
-
},
|
|
131
|
-
],
|
|
132
|
-
},
|
|
133
|
-
],
|
|
134
|
-
});
|
|
135
|
-
const result = await resolver.resolve({
|
|
136
|
-
address,
|
|
137
|
-
targetAddress: address,
|
|
138
|
-
node: mockNode,
|
|
139
|
-
request: createRouterRequest(),
|
|
140
|
-
});
|
|
141
|
-
expect(result.targetAddress.value).to.equal('o://leader/services/embeddings/method/params');
|
|
142
|
-
});
|
|
143
|
-
it('should handle hierarchical address resolution', async () => {
|
|
144
|
-
const address = new oNodeAddress('o://leader/services/embeddings-text');
|
|
145
|
-
mockNode.use = async () => createResponse({
|
|
146
|
-
data: [
|
|
147
|
-
{
|
|
148
|
-
address: 'o://leader/services/embeddings-text',
|
|
149
|
-
transports: [
|
|
150
|
-
{
|
|
151
|
-
value: '/ip4/127.0.0.1/tcp/6001',
|
|
152
|
-
type: TransportType.LIBP2P,
|
|
153
|
-
},
|
|
154
|
-
],
|
|
155
|
-
},
|
|
156
|
-
],
|
|
157
|
-
});
|
|
158
|
-
const result = await resolver.resolve({
|
|
159
|
-
address,
|
|
160
|
-
targetAddress: address,
|
|
161
|
-
node: mockNode,
|
|
162
|
-
request: createRouterRequest(),
|
|
163
|
-
});
|
|
164
|
-
expect(result.targetAddress.transports).to.have.lengthOf(1);
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
describe('buildSearchParams()', () => {
|
|
168
|
-
it('should include both staticAddress and address', () => {
|
|
169
|
-
const address = new oNodeAddress('o://leader/services/embeddings-text');
|
|
170
|
-
const params = resolver.buildSearchParams(address);
|
|
171
|
-
expect(params.staticAddress).to.equal('o://leader');
|
|
172
|
-
expect(params.address).to.equal('o://leader/services/embeddings-text');
|
|
173
|
-
});
|
|
174
|
-
it('should use root address for staticAddress', () => {
|
|
175
|
-
const address = new oNodeAddress('o://leader/deep/nested/path');
|
|
176
|
-
const params = resolver.buildSearchParams(address);
|
|
177
|
-
expect(params.staticAddress).to.equal('o://leader');
|
|
178
|
-
expect(params.address).to.equal('o://leader/deep/nested/path');
|
|
179
|
-
});
|
|
180
|
-
it('should handle static addresses', () => {
|
|
181
|
-
const address = new oNodeAddress('o://embeddings-text');
|
|
182
|
-
const params = resolver.buildSearchParams(address);
|
|
183
|
-
expect(params.staticAddress).to.equal('o://embeddings-text');
|
|
184
|
-
expect(params.address).to.equal('o://embeddings-text');
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
describe('filterSearchResults()', () => {
|
|
188
|
-
it('should filter out registry address to prevent loops', () => {
|
|
189
|
-
const results = [
|
|
190
|
-
{
|
|
191
|
-
staticAddress: RestrictedAddresses.REGISTRY,
|
|
192
|
-
address: 'o://registry',
|
|
193
|
-
},
|
|
194
|
-
{ staticAddress: 'o://service', address: 'o://service' },
|
|
195
|
-
];
|
|
196
|
-
const filtered = resolver.filterSearchResults(results, mockNode);
|
|
197
|
-
expect(filtered).to.have.lengthOf(1);
|
|
198
|
-
expect(filtered[0].staticAddress).to.equal('o://service');
|
|
199
|
-
});
|
|
200
|
-
it('should filter out self address to prevent loops', () => {
|
|
201
|
-
const results = [
|
|
202
|
-
{ address: 'o://test-node', staticAddress: 'o://test' },
|
|
203
|
-
{ address: 'o://other-node', staticAddress: 'o://other' },
|
|
204
|
-
];
|
|
205
|
-
const filtered = resolver.filterSearchResults(results, mockNode);
|
|
206
|
-
expect(filtered).to.have.lengthOf(1);
|
|
207
|
-
expect(filtered[0].address).to.equal('o://other-node');
|
|
208
|
-
});
|
|
209
|
-
it('should allow all valid results', () => {
|
|
210
|
-
const results = [
|
|
211
|
-
{ address: 'o://service-1', staticAddress: 'o://s1' },
|
|
212
|
-
{ address: 'o://service-2', staticAddress: 'o://s2' },
|
|
213
|
-
{ address: 'o://service-3', staticAddress: 'o://s3' },
|
|
214
|
-
];
|
|
215
|
-
const filtered = resolver.filterSearchResults(results, mockNode);
|
|
216
|
-
expect(filtered).to.have.lengthOf(3);
|
|
217
|
-
});
|
|
218
|
-
it('should handle empty results array', () => {
|
|
219
|
-
const filtered = resolver.filterSearchResults([], mockNode);
|
|
220
|
-
expect(filtered).to.be.an('array').that.is.empty;
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
describe('selectResult()', () => {
|
|
224
|
-
it('should select first result from multiple options', () => {
|
|
225
|
-
const results = [
|
|
226
|
-
{ address: 'o://service-1', peerId: 'peer-1' },
|
|
227
|
-
{ address: 'o://service-2', peerId: 'peer-2' },
|
|
228
|
-
{ address: 'o://service-3', peerId: 'peer-3' },
|
|
229
|
-
];
|
|
230
|
-
const selected = resolver.selectResult(results);
|
|
231
|
-
expect(selected).to.equal(results[0]);
|
|
232
|
-
expect(selected.peerId).to.equal('peer-1');
|
|
233
|
-
});
|
|
234
|
-
it('should return null for empty results', () => {
|
|
235
|
-
const selected = resolver.selectResult([]);
|
|
236
|
-
expect(selected).to.be.null;
|
|
237
|
-
});
|
|
238
|
-
it('should return single result', () => {
|
|
239
|
-
const results = [{ address: 'o://only-service', peerId: 'only-peer' }];
|
|
240
|
-
const selected = resolver.selectResult(results);
|
|
241
|
-
expect(selected).to.equal(results[0]);
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
describe('mapTransports()', () => {
|
|
245
|
-
it('should map transport objects to oNodeTransport instances', () => {
|
|
246
|
-
const result = {
|
|
247
|
-
transports: [
|
|
248
|
-
{ value: '/ip4/127.0.0.1/tcp/4001', type: TransportType.LIBP2P },
|
|
249
|
-
{ value: '/ip4/192.168.1.1/tcp/4002', type: TransportType.LIBP2P },
|
|
250
|
-
],
|
|
251
|
-
};
|
|
252
|
-
const transports = resolver.mapTransports(result);
|
|
253
|
-
expect(transports).to.have.lengthOf(2);
|
|
254
|
-
expect(transports[0]).to.be.instanceOf(oNodeTransport);
|
|
255
|
-
expect(transports[0].value).to.equal('/ip4/127.0.0.1/tcp/4001');
|
|
256
|
-
expect(transports[1].value).to.equal('/ip4/192.168.1.1/tcp/4002');
|
|
257
|
-
});
|
|
258
|
-
it('should handle empty transports array', () => {
|
|
259
|
-
const result = { transports: [] };
|
|
260
|
-
const transports = resolver.mapTransports(result);
|
|
261
|
-
expect(transports).to.be.an('array').that.is.empty;
|
|
262
|
-
});
|
|
263
|
-
it('should handle single transport', () => {
|
|
264
|
-
const result = {
|
|
265
|
-
transports: [{ value: '/memory', type: TransportType.CUSTOM }],
|
|
266
|
-
};
|
|
267
|
-
const transports = resolver.mapTransports(result);
|
|
268
|
-
expect(transports).to.have.lengthOf(1);
|
|
269
|
-
expect(transports[0].value).to.equal('/memory');
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
describe('resolveNextHopTransports()', () => {
|
|
273
|
-
it('should use leader transports when next hop is leader', () => {
|
|
274
|
-
const nextHop = new oNodeAddress(RestrictedAddresses.LEADER);
|
|
275
|
-
const targetTransports = [
|
|
276
|
-
new oNodeTransport('/ip4/192.168.1.1/tcp/9999'),
|
|
277
|
-
];
|
|
278
|
-
const resolved = resolver.resolveNextHopTransports(nextHop, targetTransports, mockNode);
|
|
279
|
-
expect(resolved).to.have.lengthOf(1);
|
|
280
|
-
expect(resolved[0].value).to.equal('/ip4/127.0.0.1/tcp/4001'); // Leader transport from mock
|
|
281
|
-
});
|
|
282
|
-
it('should use child transports when next hop is known child', () => {
|
|
283
|
-
const childAddress = new oNodeAddress('o://known-child', [
|
|
284
|
-
new oNodeTransport('/ip4/10.0.0.1/tcp/7001'),
|
|
285
|
-
]);
|
|
286
|
-
mockNode.hierarchyManager = {
|
|
287
|
-
getChild: (address) => {
|
|
288
|
-
if (address.value === 'o://known-child') {
|
|
289
|
-
return childAddress;
|
|
290
|
-
}
|
|
291
|
-
return null;
|
|
292
|
-
},
|
|
293
|
-
};
|
|
294
|
-
const nextHop = new oNodeAddress('o://known-child');
|
|
295
|
-
const targetTransports = [
|
|
296
|
-
new oNodeTransport('/ip4/192.168.1.1/tcp/9999'),
|
|
297
|
-
];
|
|
298
|
-
const resolved = resolver.resolveNextHopTransports(nextHop, targetTransports, mockNode);
|
|
299
|
-
expect(resolved).to.have.lengthOf(1);
|
|
300
|
-
expect(resolved[0].value).to.equal('/ip4/10.0.0.1/tcp/7001');
|
|
301
|
-
});
|
|
302
|
-
it('should use target transports for unknown next hop', () => {
|
|
303
|
-
const nextHop = new oNodeAddress('o://unknown-node');
|
|
304
|
-
const targetTransports = [
|
|
305
|
-
new oNodeTransport('/ip4/192.168.1.1/tcp/8001'),
|
|
306
|
-
];
|
|
307
|
-
const resolved = resolver.resolveNextHopTransports(nextHop, targetTransports, mockNode);
|
|
308
|
-
expect(resolved).to.have.lengthOf(1);
|
|
309
|
-
expect(resolved[0].value).to.equal('/ip4/192.168.1.1/tcp/8001');
|
|
310
|
-
});
|
|
311
|
-
it('should return empty array when leader has no transports', () => {
|
|
312
|
-
// Create a new mock node with leader that has no transports
|
|
313
|
-
const mockNodeNoLeaderTransports = {
|
|
314
|
-
...mockNode,
|
|
315
|
-
leader: new oNodeAddress(RestrictedAddresses.LEADER, []),
|
|
316
|
-
};
|
|
317
|
-
const nextHop = new oNodeAddress(RestrictedAddresses.LEADER);
|
|
318
|
-
const targetTransports = [
|
|
319
|
-
new oNodeTransport('/ip4/192.168.1.1/tcp/9999'),
|
|
320
|
-
];
|
|
321
|
-
const resolved = resolver.resolveNextHopTransports(nextHop, targetTransports, mockNodeNoLeaderTransports);
|
|
322
|
-
expect(resolved).to.be.an('array').that.is.empty;
|
|
323
|
-
});
|
|
324
|
-
});
|
|
325
|
-
describe('determineNextHop()', () => {
|
|
326
|
-
it('should use oAddress.next() for hierarchy routing', async () => {
|
|
327
|
-
const resolvedTarget = new oNodeAddress('o://leader/services/embeddings');
|
|
328
|
-
const searchResult = {
|
|
329
|
-
transports: [
|
|
330
|
-
{ value: '/ip4/127.0.0.1/tcp/6001', type: TransportType.LIBP2P },
|
|
331
|
-
],
|
|
332
|
-
};
|
|
333
|
-
mockNode.address = new oNodeAddress(RestrictedAddresses.LEADER);
|
|
334
|
-
const nextHop = resolver.determineNextHop(mockNode, resolvedTarget, searchResult);
|
|
335
|
-
// From leader to o://leader/services/embeddings, next hop should be o://leader/services
|
|
336
|
-
expect(nextHop.value).to.equal('o://leader/services');
|
|
337
|
-
expect(nextHop.transports).to.not.be.empty;
|
|
338
|
-
});
|
|
339
|
-
it('should set transports on next hop address', async () => {
|
|
340
|
-
const resolvedTarget = new oNodeAddress('o://leader/services');
|
|
341
|
-
const searchResult = {
|
|
342
|
-
transports: [
|
|
343
|
-
{ value: '/ip4/127.0.0.1/tcp/6001', type: TransportType.LIBP2P },
|
|
344
|
-
],
|
|
345
|
-
};
|
|
346
|
-
mockNode.address = new oNodeAddress(RestrictedAddresses.LEADER);
|
|
347
|
-
const nextHop = resolver.determineNextHop(mockNode, resolvedTarget, searchResult);
|
|
348
|
-
expect(nextHop.transports).to.not.be.undefined;
|
|
349
|
-
expect(nextHop.transports).to.not.be.empty;
|
|
350
|
-
});
|
|
351
|
-
it('should handle routing to immediate child', async () => {
|
|
352
|
-
const resolvedTarget = new oNodeAddress('o://leader/services');
|
|
353
|
-
const searchResult = {
|
|
354
|
-
transports: [
|
|
355
|
-
{ value: '/ip4/127.0.0.1/tcp/6001', type: TransportType.LIBP2P },
|
|
356
|
-
],
|
|
357
|
-
};
|
|
358
|
-
mockNode.address = new oNodeAddress(RestrictedAddresses.LEADER);
|
|
359
|
-
const nextHop = resolver.determineNextHop(mockNode, resolvedTarget, searchResult);
|
|
360
|
-
expect(nextHop.value).to.equal('o://leader/services');
|
|
361
|
-
});
|
|
362
|
-
});
|
|
363
|
-
describe('Custom resolver subclassing', () => {
|
|
364
|
-
it('should allow overriding getRegistryAddress()', () => {
|
|
365
|
-
class CustomRegistryResolver extends oSearchResolver {
|
|
366
|
-
getRegistryAddress() {
|
|
367
|
-
return new oAddress('o://custom-registry');
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
const customResolver = new CustomRegistryResolver(testAddress);
|
|
371
|
-
const registryAddress = customResolver.getRegistryAddress();
|
|
372
|
-
expect(registryAddress.value).to.equal('o://custom-registry');
|
|
373
|
-
});
|
|
374
|
-
it('should allow overriding getSearchMethod()', () => {
|
|
375
|
-
class CustomMethodResolver extends oSearchResolver {
|
|
376
|
-
getSearchMethod() {
|
|
377
|
-
return 'custom_search';
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
const customResolver = new CustomMethodResolver(testAddress);
|
|
381
|
-
const method = customResolver.getSearchMethod();
|
|
382
|
-
expect(method).to.equal('custom_search');
|
|
383
|
-
});
|
|
384
|
-
it('should allow custom result selection (e.g., round-robin)', () => {
|
|
385
|
-
class RoundRobinResolver extends oSearchResolver {
|
|
386
|
-
constructor() {
|
|
387
|
-
super(...arguments);
|
|
388
|
-
this.currentIndex = 0;
|
|
389
|
-
}
|
|
390
|
-
selectResult(results) {
|
|
391
|
-
if (results.length === 0)
|
|
392
|
-
return null;
|
|
393
|
-
const result = results[this.currentIndex % results.length];
|
|
394
|
-
this.currentIndex++;
|
|
395
|
-
return result;
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
const customResolver = new RoundRobinResolver(testAddress);
|
|
399
|
-
const results = [
|
|
400
|
-
{ address: 'o://service-1' },
|
|
401
|
-
{ address: 'o://service-2' },
|
|
402
|
-
{ address: 'o://service-3' },
|
|
403
|
-
];
|
|
404
|
-
// First call
|
|
405
|
-
let selected = customResolver.selectResult(results);
|
|
406
|
-
expect(selected.address).to.equal('o://service-1');
|
|
407
|
-
// Second call
|
|
408
|
-
selected = customResolver.selectResult(results);
|
|
409
|
-
expect(selected.address).to.equal('o://service-2');
|
|
410
|
-
// Third call
|
|
411
|
-
selected = customResolver.selectResult(results);
|
|
412
|
-
expect(selected.address).to.equal('o://service-3');
|
|
413
|
-
// Fourth call - wraps around
|
|
414
|
-
selected = customResolver.selectResult(results);
|
|
415
|
-
expect(selected.address).to.equal('o://service-1');
|
|
416
|
-
});
|
|
417
|
-
it('should allow custom filtering logic', () => {
|
|
418
|
-
class StatusFilterResolver extends oSearchResolver {
|
|
419
|
-
filterSearchResults(results, node) {
|
|
420
|
-
// First apply parent filtering
|
|
421
|
-
const filtered = super.filterSearchResults(results, node);
|
|
422
|
-
// Then apply custom filtering
|
|
423
|
-
return filtered.filter((result) => result.status === 'active');
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
const customResolver = new StatusFilterResolver(testAddress);
|
|
427
|
-
const results = [
|
|
428
|
-
{ address: 'o://service-1', status: 'active', staticAddress: 'o://s1' },
|
|
429
|
-
{
|
|
430
|
-
address: 'o://service-2',
|
|
431
|
-
status: 'inactive',
|
|
432
|
-
staticAddress: 'o://s2',
|
|
433
|
-
},
|
|
434
|
-
{ address: 'o://service-3', status: 'active', staticAddress: 'o://s3' },
|
|
435
|
-
];
|
|
436
|
-
const filtered = customResolver.filterSearchResults(results, mockNode);
|
|
437
|
-
expect(filtered).to.have.lengthOf(2);
|
|
438
|
-
expect(filtered.every((r) => r.status === 'active')).to.be.true;
|
|
439
|
-
});
|
|
440
|
-
it('should allow direct routing by overriding determineNextHop()', () => {
|
|
441
|
-
class DirectRoutingResolver extends oSearchResolver {
|
|
442
|
-
determineNextHop(node, resolvedTargetAddress, searchResult) {
|
|
443
|
-
// Always route directly to target, bypassing hierarchy
|
|
444
|
-
const targetTransports = this.mapTransports(searchResult);
|
|
445
|
-
resolvedTargetAddress.setTransports(targetTransports);
|
|
446
|
-
return resolvedTargetAddress;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
const customResolver = new DirectRoutingResolver(testAddress);
|
|
450
|
-
const resolvedTarget = new oNodeAddress('o://leader/services/embeddings');
|
|
451
|
-
const searchResult = {
|
|
452
|
-
transports: [
|
|
453
|
-
{ value: '/ip4/127.0.0.1/tcp/6001', type: TransportType.LIBP2P },
|
|
454
|
-
],
|
|
455
|
-
};
|
|
456
|
-
mockNode.address = new oNodeAddress(RestrictedAddresses.LEADER);
|
|
457
|
-
const nextHop = customResolver.determineNextHop(mockNode, resolvedTarget, searchResult);
|
|
458
|
-
// Direct routing: next hop is the target itself
|
|
459
|
-
expect(nextHop.value).to.equal('o://leader/services/embeddings');
|
|
460
|
-
expect(nextHop.transports).to.have.lengthOf(1);
|
|
461
|
-
});
|
|
462
|
-
});
|
|
463
|
-
describe('Integration with registry calls', () => {
|
|
464
|
-
it('should call node.use() with correct registry address', async () => {
|
|
465
|
-
const address = new oNodeAddress('o://test-service');
|
|
466
|
-
let capturedAddress;
|
|
467
|
-
let capturedRequest;
|
|
468
|
-
mockNode.use = async (addr, req) => {
|
|
469
|
-
capturedAddress = addr;
|
|
470
|
-
capturedRequest = req;
|
|
471
|
-
return createResponse({ data: [] });
|
|
472
|
-
};
|
|
473
|
-
await resolver.resolve({
|
|
474
|
-
address,
|
|
475
|
-
targetAddress: address,
|
|
476
|
-
node: mockNode,
|
|
477
|
-
request: createRouterRequest(),
|
|
478
|
-
});
|
|
479
|
-
expect(capturedAddress?.value).to.equal(RestrictedAddresses.REGISTRY);
|
|
480
|
-
expect(capturedRequest.method).to.equal('search');
|
|
481
|
-
});
|
|
482
|
-
it('should pass correct search parameters to registry', async () => {
|
|
483
|
-
const address = new oNodeAddress('o://leader/services/embeddings');
|
|
484
|
-
let capturedParams;
|
|
485
|
-
mockNode.use = async (addr, req) => {
|
|
486
|
-
capturedParams = req.params;
|
|
487
|
-
return createResponse({ data: [] });
|
|
488
|
-
};
|
|
489
|
-
await resolver.resolve({
|
|
490
|
-
address,
|
|
491
|
-
targetAddress: address,
|
|
492
|
-
node: mockNode,
|
|
493
|
-
request: createRouterRequest(),
|
|
494
|
-
});
|
|
495
|
-
expect(capturedParams.staticAddress).to.equal('o://leader');
|
|
496
|
-
expect(capturedParams.address).to.equal('o://leader/services/embeddings');
|
|
497
|
-
});
|
|
498
|
-
it('should handle registry errors gracefully', async () => {
|
|
499
|
-
const address = new oNodeAddress('o://test-service');
|
|
500
|
-
mockNode.use = async () => {
|
|
501
|
-
throw new Error('Registry unavailable');
|
|
502
|
-
};
|
|
503
|
-
// Should throw error since registry is unavailable
|
|
504
|
-
try {
|
|
505
|
-
await resolver.resolve({
|
|
506
|
-
address,
|
|
507
|
-
targetAddress: address,
|
|
508
|
-
node: mockNode,
|
|
509
|
-
request: createRouterRequest(),
|
|
510
|
-
});
|
|
511
|
-
expect.fail('Should have thrown error');
|
|
512
|
-
}
|
|
513
|
-
catch (error) {
|
|
514
|
-
expect(error.message).to.equal('Registry unavailable');
|
|
515
|
-
}
|
|
516
|
-
});
|
|
517
|
-
});
|
|
518
|
-
describe('Address duplication bug fix', () => {
|
|
519
|
-
it('should NOT duplicate path segments when registry returns full hierarchical address', async () => {
|
|
520
|
-
// This test verifies the fix for the bug where o://services/embeddings-text
|
|
521
|
-
// was being resolved to o://leader/services/embeddings-text/services/embeddings-text
|
|
522
|
-
const address = new oNodeAddress('o://services/embeddings-text');
|
|
523
|
-
mockNode.use = async () => createResponse({
|
|
524
|
-
data: [
|
|
525
|
-
{
|
|
526
|
-
address: 'o://leader/services/embeddings-text',
|
|
527
|
-
staticAddress: 'o://embeddings-text',
|
|
528
|
-
transports: [
|
|
529
|
-
{
|
|
530
|
-
value: '/ip4/127.0.0.1/tcp/6001',
|
|
531
|
-
type: TransportType.LIBP2P,
|
|
532
|
-
},
|
|
533
|
-
],
|
|
534
|
-
},
|
|
535
|
-
],
|
|
536
|
-
});
|
|
537
|
-
const result = await resolver.resolve({
|
|
538
|
-
address,
|
|
539
|
-
targetAddress: address,
|
|
540
|
-
node: mockNode,
|
|
541
|
-
request: createRouterRequest(),
|
|
542
|
-
});
|
|
543
|
-
// Should be o://leader/services/embeddings-text, NOT o://leader/services/embeddings-text/embeddings-text
|
|
544
|
-
expect(result.targetAddress.value).to.equal('o://leader/services/embeddings-text');
|
|
545
|
-
});
|
|
546
|
-
it('should NOT duplicate when calling via static address', async () => {
|
|
547
|
-
const address = new oNodeAddress('o://embeddings-text');
|
|
548
|
-
mockNode.use = async () => createResponse({
|
|
549
|
-
data: [
|
|
550
|
-
{
|
|
551
|
-
address: 'o://leader/services/embeddings-text',
|
|
552
|
-
staticAddress: 'o://embeddings-text',
|
|
553
|
-
transports: [
|
|
554
|
-
{
|
|
555
|
-
value: '/ip4/127.0.0.1/tcp/6001',
|
|
556
|
-
type: TransportType.LIBP2P,
|
|
557
|
-
},
|
|
558
|
-
],
|
|
559
|
-
},
|
|
560
|
-
],
|
|
561
|
-
});
|
|
562
|
-
const result = await resolver.resolve({
|
|
563
|
-
address,
|
|
564
|
-
targetAddress: address,
|
|
565
|
-
node: mockNode,
|
|
566
|
-
request: createRouterRequest(),
|
|
567
|
-
});
|
|
568
|
-
expect(result.targetAddress.value).to.equal('o://leader/services/embeddings-text');
|
|
569
|
-
});
|
|
570
|
-
it('should still append legitimate extra params beyond the service name', async () => {
|
|
571
|
-
// If someone calls o://embeddings-text/custom/path, we should preserve /custom/path
|
|
572
|
-
const address = new oNodeAddress('o://embeddings-text/custom/path');
|
|
573
|
-
mockNode.use = async () => createResponse({
|
|
574
|
-
data: [
|
|
575
|
-
{
|
|
576
|
-
address: 'o://leader/services/embeddings-text',
|
|
577
|
-
staticAddress: 'o://embeddings-text',
|
|
578
|
-
transports: [
|
|
579
|
-
{
|
|
580
|
-
value: '/ip4/127.0.0.1/tcp/6001',
|
|
581
|
-
type: TransportType.LIBP2P,
|
|
582
|
-
},
|
|
583
|
-
],
|
|
584
|
-
},
|
|
585
|
-
],
|
|
586
|
-
});
|
|
587
|
-
const result = await resolver.resolve({
|
|
588
|
-
address,
|
|
589
|
-
targetAddress: address,
|
|
590
|
-
node: mockNode,
|
|
591
|
-
request: createRouterRequest(),
|
|
592
|
-
});
|
|
593
|
-
// Should append the extra /custom/path
|
|
594
|
-
expect(result.targetAddress.value).to.equal('o://leader/services/embeddings-text/custom/path');
|
|
595
|
-
});
|
|
596
|
-
});
|
|
597
|
-
describe('Edge cases', () => {
|
|
598
|
-
it('should handle address with long nested paths', async () => {
|
|
599
|
-
const address = new oNodeAddress('o://leader/a/b/c/d/e/f');
|
|
600
|
-
mockNode.use = async () => createResponse({
|
|
601
|
-
data: [
|
|
602
|
-
{
|
|
603
|
-
address: 'o://leader/a/b/c/d/e/f',
|
|
604
|
-
transports: [{ value: '/memory', type: TransportType.CUSTOM }],
|
|
605
|
-
},
|
|
606
|
-
],
|
|
607
|
-
});
|
|
608
|
-
const result = await resolver.resolve({
|
|
609
|
-
address,
|
|
610
|
-
targetAddress: address,
|
|
611
|
-
node: mockNode,
|
|
612
|
-
request: createRouterRequest(),
|
|
613
|
-
});
|
|
614
|
-
expect(result.targetAddress.value).to.equal('o://leader/a/b/c/d/e/f');
|
|
615
|
-
});
|
|
616
|
-
it('should handle static addresses correctly', async () => {
|
|
617
|
-
const address = new oNodeAddress('o://embeddings-text');
|
|
618
|
-
mockNode.use = async () => createResponse({
|
|
619
|
-
data: [
|
|
620
|
-
{
|
|
621
|
-
address: 'o://leader/services/embeddings',
|
|
622
|
-
staticAddress: 'o://embeddings-text',
|
|
623
|
-
transports: [
|
|
624
|
-
{
|
|
625
|
-
value: '/ip4/127.0.0.1/tcp/6001',
|
|
626
|
-
type: TransportType.LIBP2P,
|
|
627
|
-
},
|
|
628
|
-
],
|
|
629
|
-
},
|
|
630
|
-
],
|
|
631
|
-
});
|
|
632
|
-
const result = await resolver.resolve({
|
|
633
|
-
address,
|
|
634
|
-
targetAddress: address,
|
|
635
|
-
node: mockNode,
|
|
636
|
-
request: createRouterRequest(),
|
|
637
|
-
});
|
|
638
|
-
expect(result.targetAddress.value).to.equal('o://leader/services/embeddings');
|
|
639
|
-
});
|
|
640
|
-
it('should handle when leader is undefined', async () => {
|
|
641
|
-
const address = new oNodeAddress('o://test');
|
|
642
|
-
// Create a new mock node with undefined leader
|
|
643
|
-
const mockNodeNoLeader = {
|
|
644
|
-
...mockNode,
|
|
645
|
-
leader: undefined,
|
|
646
|
-
};
|
|
647
|
-
mockNodeNoLeader.use = async () => createResponse({
|
|
648
|
-
data: [
|
|
649
|
-
{
|
|
650
|
-
address: 'o://test',
|
|
651
|
-
transports: [{ value: '/memory', type: TransportType.CUSTOM }],
|
|
652
|
-
},
|
|
653
|
-
],
|
|
654
|
-
});
|
|
655
|
-
const result = await resolver.resolve({
|
|
656
|
-
address,
|
|
657
|
-
targetAddress: address,
|
|
658
|
-
node: mockNodeNoLeader,
|
|
659
|
-
request: createRouterRequest(),
|
|
660
|
-
});
|
|
661
|
-
// Should still work, just won't have leader transports
|
|
662
|
-
expect(result.targetAddress.value).to.equal('o://test');
|
|
663
|
-
});
|
|
664
|
-
it('should handle multiple transports in search result', async () => {
|
|
665
|
-
const address = new oNodeAddress('o://multi-transport');
|
|
666
|
-
mockNode.use = async () => createResponse({
|
|
667
|
-
data: [
|
|
668
|
-
{
|
|
669
|
-
address: 'o://multi-transport',
|
|
670
|
-
transports: [
|
|
671
|
-
{
|
|
672
|
-
value: '/ip4/127.0.0.1/tcp/4001',
|
|
673
|
-
type: TransportType.LIBP2P,
|
|
674
|
-
},
|
|
675
|
-
{
|
|
676
|
-
value: '/ip4/192.168.1.1/tcp/4002',
|
|
677
|
-
type: TransportType.LIBP2P,
|
|
678
|
-
},
|
|
679
|
-
{ value: '/memory', type: TransportType.CUSTOM },
|
|
680
|
-
],
|
|
681
|
-
},
|
|
682
|
-
],
|
|
683
|
-
});
|
|
684
|
-
const result = await resolver.resolve({
|
|
685
|
-
address,
|
|
686
|
-
targetAddress: address,
|
|
687
|
-
node: mockNode,
|
|
688
|
-
request: createRouterRequest(),
|
|
689
|
-
});
|
|
690
|
-
expect(result.targetAddress.transports).to.have.lengthOf(3);
|
|
691
|
-
});
|
|
692
|
-
});
|
|
693
|
-
});
|