soajs.controller 4.3.22 → 4.3.24

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.
@@ -0,0 +1,572 @@
1
+ "use strict";
2
+
3
+ const helper = require("../../../helper.js");
4
+ const tools = helper.requireModule('./mw/soajsRoutes/mcp/tools');
5
+ const mcp = helper.requireModule('./mw/soajsRoutes/mcp/index');
6
+ const aclLib = helper.requireModule('./mw/soajsRoutes/lib/acl');
7
+ const assert = require('assert');
8
+ const http = require('http');
9
+ const {Readable} = require('stream');
10
+
11
+ const config = {"enabled": true, "ttl": 300000, "errorTtl": 30000, "timeout": 2000, "maxTools": 300};
12
+ const configuration = {
13
+ core: {
14
+ error: {
15
+ getError: (code) => {
16
+ return {code: code};
17
+ }
18
+ }
19
+ },
20
+ serviceName: "controller",
21
+ serviceVersion: "1"
22
+ };
23
+
24
+ let log = {
25
+ "errors": [],
26
+ debug: () => {
27
+ },
28
+ error: (msg) => {
29
+ log.errors.push(msg);
30
+ }
31
+ };
32
+
33
+ /**
34
+ * A request as the gateway sees it after mt: a key, a user ACL, the registry.
35
+ */
36
+ let makeReq = (opts) => {
37
+ opts = opts || {};
38
+ let req = new Readable({
39
+ read() {
40
+ }
41
+ });
42
+ if (opts.body !== undefined) {
43
+ req.push(opts.body);
44
+ }
45
+ req.push(null);
46
+ req.method = opts.method || "POST";
47
+ req.headers = Object.assign({key: "extKey"}, opts.headers || {});
48
+ req.query = opts.query || {};
49
+ req.getClientIP = () => "10.0.0.5";
50
+ req.soajs = {
51
+ log: log,
52
+ registry: {
53
+ serviceConfig: {ports: {maintenanceInc: 1000}},
54
+ services: Object.assign({controller: {port: opts.gatewayPort || 4000}}, opts.services || {}),
55
+ custom: {gateway: {value: {mcp: (opts.mcp === undefined) ? {enabled: true, timeout: 2000} : opts.mcp}}}
56
+ },
57
+ awareness: {
58
+ getHost: function () {
59
+ let cb = arguments[arguments.length - 1];
60
+ return cb(opts.host === undefined ? "127.0.0.1" : opts.host);
61
+ }
62
+ },
63
+ controller: {
64
+ serviceParams: {
65
+ keyObj: {application: {package: "PCK"}},
66
+ packObj: {}
67
+ }
68
+ },
69
+ controllerResponse: (response) => {
70
+ req.controllerResponse = response;
71
+ if (opts.onControllerResponse) {
72
+ opts.onControllerResponse(response);
73
+ }
74
+ }
75
+ };
76
+ if (opts.acl) {
77
+ req.soajs.uracDriver = {
78
+ getAcl: () => opts.acl,
79
+ getAllowedPackages: () => ["PCK"]
80
+ };
81
+ }
82
+ return req;
83
+ };
84
+
85
+ let makeRes = (cb) => {
86
+ let res = {
87
+ writeHead: (status, headers) => {
88
+ res.status = status;
89
+ res.headers = headers;
90
+ },
91
+ end: (text) => {
92
+ cb(res.status, res.headers, text ? JSON.parse(text) : null);
93
+ }
94
+ };
95
+ return res;
96
+ };
97
+
98
+ /**
99
+ * A local server: handler(req, body, res).
100
+ */
101
+ let serve = (handler, cb) => {
102
+ let server = http.createServer((req, res) => {
103
+ let chunks = [];
104
+ req.on("data", (c) => chunks.push(c));
105
+ req.on("end", () => handler(req, Buffer.concat(chunks).toString(), res));
106
+ });
107
+ server.listen(0, "127.0.0.1", () => cb(server, server.address().port));
108
+ };
109
+
110
+ let json = (res, status, body) => {
111
+ res.writeHead(status, {"Content-Type": "application/json"});
112
+ res.end(JSON.stringify(body));
113
+ };
114
+
115
+ const coreTools = [
116
+ {
117
+ "m": "get", "v": "/users", "l": "List users", "group": "Admin",
118
+ "inputSchema": {"type": "object", "properties": {"limit": {"type": "integer"}}},
119
+ "fields": {"limit": {"in": "query", "path": ["limit"]}}
120
+ },
121
+ {
122
+ "m": "post", "v": "/user/:id", "l": "Update user",
123
+ "inputSchema": {"type": "object", "properties": {"id": {"type": "string"}, "name": {"type": "string"}}, "required": ["id"]},
124
+ "fields": {"id": {"in": "params", "path": ["id"]}, "name": {"in": "body", "path": ["profile", "name"]}}
125
+ },
126
+ {
127
+ "m": "delete", "v": "/user/:id", "l": "Delete user",
128
+ "inputSchema": {"type": "object", "properties": {"id": {"type": "string"}}, "required": ["id"]},
129
+ "fields": {"id": {"in": "params", "path": ["id"]}}
130
+ }
131
+ ];
132
+
133
+ describe("Unit test for: mw - soajsRoutes mcp", () => {
134
+
135
+ beforeEach(() => {
136
+ tools.clearCache();
137
+ log.errors = [];
138
+ });
139
+
140
+ describe("getConfig", () => {
141
+ it("off unless enabled is true", () => {
142
+ assert.strictEqual(tools.getConfig(makeReq({mcp: null})), null);
143
+ assert.strictEqual(tools.getConfig(makeReq({mcp: {enabled: "true"}})), null);
144
+ });
145
+ it("defaults and overrides", () => {
146
+ assert.deepStrictEqual(tools.getConfig(makeReq({mcp: {enabled: true}})), {
147
+ "enabled": true, "ttl": 300000, "errorTtl": 30000, "timeout": 10000, "maxTools": 300
148
+ });
149
+ let c = tools.getConfig(makeReq({mcp: {enabled: true, ttl: 1000, timeout: -1, maxTools: "5"}}));
150
+ assert.strictEqual(c.ttl, 1000);
151
+ assert.strictEqual(c.timeout, 10000);
152
+ assert.strictEqual(c.maxTools, 300);
153
+ });
154
+ });
155
+
156
+ describe("maintenancePort", () => {
157
+ let registry = {
158
+ serviceConfig: {ports: {maintenanceInc: 1000}},
159
+ services: {
160
+ core: {port: 4001},
161
+ inherit: {port: 4002, maintenance: {port: {type: "inherit"}}},
162
+ maint: {port: 4003, maintenance: {port: {type: "maintenance"}}},
163
+ custom: {port: 4004, maintenance: {port: {type: "custom", value: "6000"}}},
164
+ bad: {port: 4005, maintenance: {port: {type: "custom", value: "x"}}}
165
+ }
166
+ };
167
+ it("the way awareness finds the heartbeat", () => {
168
+ assert.strictEqual(tools.maintenancePort(registry, "core"), 5001);
169
+ assert.strictEqual(tools.maintenancePort(registry, "inherit"), 4002);
170
+ assert.strictEqual(tools.maintenancePort(registry, "maint"), 5003);
171
+ assert.strictEqual(tools.maintenancePort(registry, "custom"), 6000);
172
+ assert.strictEqual(tools.maintenancePort(registry, "bad"), 5005);
173
+ });
174
+ });
175
+
176
+ describe("toolsFromAnswer", () => {
177
+ let swagger = {
178
+ "openapi": "3.0.0",
179
+ "paths": {"/hello": {"get": {"summary": "Hello", "parameters": [{"name": "who", "in": "query", "schema": {"type": "string"}}]}}}
180
+ };
181
+ it("tools of a soajs service", () => {
182
+ assert.deepStrictEqual(tools.toolsFromAnswer({result: true, data: {tools: coreTools}}), coreTools);
183
+ });
184
+ it("a swagger object or YAML text of an SDK service", () => {
185
+ let fromObject = tools.toolsFromAnswer({result: true, data: {swagger: swagger}});
186
+ assert.strictEqual(fromObject.length, 1);
187
+ assert.strictEqual(fromObject[0].v, "/hello");
188
+ assert.deepStrictEqual(fromObject[0].fields, {"who": {"in": "query", "path": ["who"]}});
189
+ let yamlText = "swagger: '2.0'\npaths:\n /hi/{name}:\n get:\n summary: Hi\n";
190
+ let fromYaml = tools.toolsFromAnswer({result: true, data: {swagger: yamlText}});
191
+ assert.strictEqual(fromYaml[0].v, "/hi/:name");
192
+ });
193
+ it("nothing usable", () => {
194
+ assert.deepStrictEqual(tools.toolsFromAnswer(null), []);
195
+ assert.deepStrictEqual(tools.toolsFromAnswer({result: false}), []);
196
+ assert.deepStrictEqual(tools.toolsFromAnswer({result: true}), []);
197
+ assert.deepStrictEqual(tools.toolsFromAnswer({result: true, data: {tools: []}}), []);
198
+ assert.deepStrictEqual(tools.toolsFromAnswer({result: true, data: {}}), []);
199
+ });
200
+ });
201
+
202
+ describe("toolName", () => {
203
+ it("allowed characters, operationId, unique, 64 characters", () => {
204
+ let taken = {};
205
+ assert.strictEqual(tools.toolName("urac", {m: "get", v: "/admin/users"}, taken), "urac_get_admin_users");
206
+ assert.strictEqual(tools.toolName("urac", {m: "get", v: "/admin/users"}, taken), "urac_get_admin_users_2");
207
+ assert.strictEqual(tools.toolName("urac", {m: "post", v: "/user/:id/{x}"}, taken), "urac_post_user_id_x");
208
+ assert.strictEqual(tools.toolName("pets", {m: "get", v: "/pets", operationId: "listPets"}, taken), "pets_listPets");
209
+ let long = tools.toolName("s", {m: "get", v: "/" + "a".repeat(100)}, taken);
210
+ assert.strictEqual(long.length, 64);
211
+ let long2 = tools.toolName("s", {m: "get", v: "/" + "a".repeat(100)}, taken);
212
+ assert.strictEqual(long2.length, 64);
213
+ assert.ok(long2.endsWith("_2"));
214
+ });
215
+ });
216
+
217
+ describe("allowed", () => {
218
+ it("a service that is not restricted allows all its apis", () => {
219
+ assert.ok(tools.allowed({version: "1"}, {m: "delete", v: "/x"}));
220
+ });
221
+ it("a restricted service allows the apis it lists", () => {
222
+ let acl = {version: "1", get: ["/users"], del: ["/user/:id"]};
223
+ assert.ok(tools.allowed(acl, {m: "get", v: "/users"}));
224
+ assert.ok(!tools.allowed(acl, {m: "get", v: "/user/:id"}));
225
+ assert.ok(tools.allowed(acl, {m: "delete", v: "/user/:id"}));
226
+ assert.ok(!tools.allowed(acl, {m: "post", v: "/users"}));
227
+ });
228
+ });
229
+
230
+ describe("buildCall", () => {
231
+ it("path params, query and nested body", () => {
232
+ let call = tools.buildCall(coreTools[1], {id: "a b/c", name: "Ann", other: 1});
233
+ assert.deepStrictEqual(call, {"path": "/user/a%20b%2Fc", "query": "", "body": {"profile": {"name": "Ann"}}});
234
+ call = tools.buildCall(coreTools[0], {limit: 5});
235
+ assert.deepStrictEqual(call, {"path": "/users", "query": "?limit=5", "body": undefined});
236
+ });
237
+ it("arrays and objects in the query, whole body", () => {
238
+ let tool = {
239
+ m: "post", v: "/x",
240
+ fields: {tags: {in: "query", path: ["tags"]}, filter: {in: "query", path: ["filter"]}, items: {in: "body", path: []}}
241
+ };
242
+ let call = tools.buildCall(tool, {tags: ["a", "b"], filter: {a: 1}, items: [1, 2]});
243
+ assert.strictEqual(call.query, "?tags=a&tags=b&filter=%7B%22a%22%3A1%7D");
244
+ assert.deepStrictEqual(call.body, [1, 2]);
245
+ });
246
+ it("optional segments and a missing path param", () => {
247
+ let tool = {m: "get", v: "/file/:folder{/:name}", fields: {folder: {in: "params", path: ["folder"]}, name: {in: "params", path: ["name"]}}};
248
+ assert.strictEqual(tools.buildCall(tool, {folder: "f"}).path, "/file/f");
249
+ assert.strictEqual(tools.buildCall(tool, {folder: "f", name: "n"}).path, "/file/f/n");
250
+ assert.deepStrictEqual(tools.buildCall(tool, {name: "n"}), {"error": "Missing path parameter: folder"});
251
+ assert.deepStrictEqual(tools.buildCall(coreTools[2], "not an object"), {"error": "Missing path parameter: id"});
252
+ });
253
+ });
254
+
255
+ describe("serviceTools, listTools and callTool against local servers", () => {
256
+ let service = null;
257
+ let servicePort = null;
258
+ let asked = 0;
259
+ let answer = {result: true, data: {tools: coreTools}};
260
+
261
+ before((done) => {
262
+ serve((req, body, res) => {
263
+ asked++;
264
+ if (req.url === "/mcp/tools") {
265
+ return json(res, 200, answer);
266
+ }
267
+ return json(res, 404, {});
268
+ }, (server, port) => {
269
+ service = server;
270
+ servicePort = port;
271
+ done();
272
+ });
273
+ });
274
+ after((done) => {
275
+ service.close(done);
276
+ });
277
+
278
+ let services = () => {
279
+ return {
280
+ urac: {port: servicePort, maintenance: {port: {type: "inherit"}}},
281
+ down: {port: 1, maintenance: {port: {type: "inherit"}}}
282
+ };
283
+ };
284
+
285
+ it("asks the service once, then from the cache", (done) => {
286
+ asked = 0;
287
+ let req = makeReq({services: services()});
288
+ tools.serviceTools(req, "urac", "1", config).then((list) => {
289
+ assert.deepStrictEqual(list, coreTools);
290
+ return tools.serviceTools(req, "urac", "1", config);
291
+ }).then((list) => {
292
+ assert.deepStrictEqual(list, coreTools);
293
+ assert.strictEqual(asked, 1);
294
+ done();
295
+ }).catch(done);
296
+ });
297
+
298
+ it("a service that does not answer, is not in the registry or has no host has no tools", (done) => {
299
+ let req = makeReq({services: services()});
300
+ tools.serviceTools(req, "down", "1", config).then((list) => {
301
+ assert.deepStrictEqual(list, []);
302
+ assert.ok(log.errors[0].indexOf("could not get the tools of [down@1]") !== -1);
303
+ return tools.serviceTools(req, "missing", "1", config);
304
+ }).then((list) => {
305
+ assert.deepStrictEqual(list, []);
306
+ return tools.serviceTools(makeReq({services: services(), host: null}), "urac", "1", config);
307
+ }).then((list) => {
308
+ assert.deepStrictEqual(list, []);
309
+ done();
310
+ }).catch(done);
311
+ });
312
+
313
+ it("listTools: the tools the ACL allows, named, annotated, the gateway left out", (done) => {
314
+ let acl = {
315
+ urac: {"1": {access: true, apisPermission: "restricted", get: [{group: "g", apis: {"/users": {}}}], post: {apis: {"/user/:id": {}}}}},
316
+ controller: {"1": {access: true}}
317
+ };
318
+ let req = makeReq({services: services(), acl: acl});
319
+ tools.listTools(req, configuration, config).then((list) => {
320
+ assert.deepStrictEqual(list.tools.map((t) => t.name), ["urac_get_users", "urac_post_user_id"]);
321
+ assert.deepStrictEqual(list.tools[0], {
322
+ "name": "urac_get_users",
323
+ "title": "List users",
324
+ "description": "List users [urac: GET /users]",
325
+ "inputSchema": coreTools[0].inputSchema,
326
+ "annotations": {"readOnlyHint": true, "destructiveHint": false, "openWorldHint": false}
327
+ });
328
+ assert.deepStrictEqual(list.index.urac_post_user_id, {"service": "urac", "version": "1", "tool": coreTools[1]});
329
+ done();
330
+ }).catch(done);
331
+ });
332
+
333
+ it("listTools: maxTools caps the list, no ACL gives no tools", (done) => {
334
+ let acl = {urac: {"1": {access: true}}};
335
+ tools.listTools(makeReq({services: services(), acl: acl}), configuration, Object.assign({}, config, {maxTools: 2})).then((list) => {
336
+ assert.strictEqual(list.tools.length, 2);
337
+ assert.strictEqual(list.tools[1].annotations.readOnlyHint, false);
338
+ return tools.listTools(makeReq({services: services()}), configuration, config);
339
+ }).then((list) => {
340
+ assert.deepStrictEqual(list.tools, []);
341
+ done();
342
+ }).catch(done);
343
+ });
344
+
345
+ describe("callTool", () => {
346
+ let gateway = null;
347
+ let gatewayPort = null;
348
+ let received = null;
349
+ let reply = null;
350
+
351
+ before((done) => {
352
+ serve((req, body, res) => {
353
+ received = {method: req.method, url: req.url, headers: req.headers, body: body};
354
+ return json(res, reply.status, reply.body);
355
+ }, (server, port) => {
356
+ gateway = server;
357
+ gatewayPort = port;
358
+ done();
359
+ });
360
+ });
361
+ after((done) => {
362
+ gateway.close(done);
363
+ });
364
+
365
+ let entry = {service: "urac", version: "2", tool: coreTools[1]};
366
+
367
+ it("the api call goes through the gateway port with the caller's credentials", (done) => {
368
+ reply = {status: 200, body: {result: true, data: {updated: true}}};
369
+ let req = makeReq({
370
+ gatewayPort: gatewayPort,
371
+ headers: {"authorization": "Bearer abc", "device-id": "d1", "idempotency-key": "k1", "cookie": "no"},
372
+ query: {access_token: "tok"}
373
+ });
374
+ tools.callTool(req, configuration, config, entry, {id: "u1", name: "Ann"}).then((result) => {
375
+ assert.strictEqual(received.method, "POST");
376
+ assert.strictEqual(received.url, "/urac/v2/user/u1");
377
+ assert.deepStrictEqual(JSON.parse(received.body), {profile: {name: "Ann"}});
378
+ assert.strictEqual(received.headers.key, "extKey");
379
+ assert.strictEqual(received.headers.authorization, "Bearer abc");
380
+ assert.strictEqual(received.headers.access_token, "tok");
381
+ assert.strictEqual(received.headers["device-id"], "d1");
382
+ assert.strictEqual(received.headers["idempotency-key"], "k1");
383
+ assert.strictEqual(received.headers["x-forwarded-for"], "10.0.0.5");
384
+ assert.ok(!received.headers.cookie);
385
+ assert.deepStrictEqual(result, {
386
+ "content": [{"type": "text", "text": "{\"updated\":true}"}],
387
+ "isError": false,
388
+ "structuredContent": {"updated": true}
389
+ });
390
+ done();
391
+ }).catch(done);
392
+ });
393
+
394
+ it("a get has no body, an array answer has no structuredContent", (done) => {
395
+ reply = {status: 200, body: {result: true, data: [1, 2]}};
396
+ let req = makeReq({gatewayPort: gatewayPort});
397
+ tools.callTool(req, configuration, config, {service: "urac", version: "1", tool: coreTools[0]}, {limit: 3}).then((result) => {
398
+ assert.strictEqual(received.method, "GET");
399
+ assert.strictEqual(received.url, "/urac/v1/users?limit=3");
400
+ assert.strictEqual(received.body, "");
401
+ assert.deepStrictEqual(result, {"content": [{"type": "text", "text": "[1,2]"}], "isError": false});
402
+ done();
403
+ }).catch(done);
404
+ });
405
+
406
+ it("an api error is a tool error", (done) => {
407
+ reply = {status: 403, body: {result: false, errors: {codes: [154], details: [{code: 154, message: "denied"}]}}};
408
+ tools.callTool(makeReq({gatewayPort: gatewayPort}), configuration, config, entry, {id: "u1"}).then((result) => {
409
+ assert.strictEqual(result.isError, true);
410
+ assert.ok(result.content[0].text.indexOf("denied") !== -1);
411
+ assert.ok(!result.structuredContent);
412
+ reply = {status: 200, body: {result: false, errors: {codes: [400]}}};
413
+ return tools.callTool(makeReq({gatewayPort: gatewayPort}), configuration, config, entry, {id: "u1"});
414
+ }).then((result) => {
415
+ assert.strictEqual(result.isError, true);
416
+ done();
417
+ }).catch(done);
418
+ });
419
+
420
+ it("a missing path param and a gateway that does not answer", (done) => {
421
+ tools.callTool(makeReq({gatewayPort: gatewayPort}), configuration, config, entry, {}).then((result) => {
422
+ assert.deepStrictEqual(result, {"content": [{"type": "text", "text": "Missing path parameter: id"}], "isError": true});
423
+ return tools.callTool(makeReq({gatewayPort: 1}), configuration, config, entry, {id: "u1"});
424
+ }).then((result) => {
425
+ assert.strictEqual(result.isError, true);
426
+ assert.ok(result.content[0].text.indexOf("The call failed") === 0);
427
+ done();
428
+ }).catch(done);
429
+ });
430
+ });
431
+
432
+ describe("the /soajs/mcp route", () => {
433
+ let route = mcp(configuration);
434
+
435
+ let rpc = (opts, cb) => {
436
+ let req = makeReq(Object.assign({services: services(), acl: {urac: {"1": {access: true}}}}, opts, {
437
+ onControllerResponse: (response) => cb(null, null, response)
438
+ }));
439
+ route(req, {}, () => {
440
+ req.soajs.controller.gotoservice(req, makeRes(cb));
441
+ });
442
+ return req;
443
+ };
444
+ let message = (body) => {
445
+ return JSON.stringify(Object.assign({jsonrpc: "2.0", id: 1}, body));
446
+ };
447
+
448
+ it("needs a key, mimics a gateway api without changing the registry", (done) => {
449
+ let req = makeReq({headers: {key: ""}, onControllerResponse: (response) => {
450
+ assert.deepStrictEqual(response, {code: 132});
451
+ }});
452
+ route(req, {}, () => {
453
+ done(new Error("should not continue"));
454
+ });
455
+ let ok = makeReq({});
456
+ let shared = ok.soajs.registry.services.controller;
457
+ route(ok, {}, () => {
458
+ assert.strictEqual(ok.soajs.controller.serviceParams.path, "/soajs/mcp");
459
+ assert.deepStrictEqual(ok.soajs.controller.serviceParams.registry.versions["1"].apis, [{"l": "MCP", "v": "/soajs/mcp", "m": "post"}]);
460
+ assert.ok(!shared.versions);
461
+ done();
462
+ });
463
+ });
464
+
465
+ it("off: 190", (done) => {
466
+ rpc({mcp: null, body: message({method: "ping"})}, (status, headers, body) => {
467
+ assert.deepStrictEqual(body, {"status": 404, "code": 190, "msg": "MCP is not enabled in this environment."});
468
+ done();
469
+ });
470
+ });
471
+
472
+ it("POST only", (done) => {
473
+ rpc({method: "GET"}, (status, headers, body) => {
474
+ assert.strictEqual(status, 405);
475
+ assert.strictEqual(headers.Allow, "POST");
476
+ assert.strictEqual(body.error.code, -32600);
477
+ done();
478
+ });
479
+ });
480
+
481
+ it("parse error, batch, invalid request", (done) => {
482
+ rpc({body: "{nope"}, (status, headers, body) => {
483
+ assert.strictEqual(status, 400);
484
+ assert.strictEqual(body.error.code, -32700);
485
+ rpc({body: "[]"}, (status, headers, body) => {
486
+ assert.strictEqual(body.error.code, -32600);
487
+ rpc({body: JSON.stringify({jsonrpc: "1.0", id: 7, method: "ping"})}, (status, headers, body) => {
488
+ assert.strictEqual(status, 400);
489
+ assert.deepStrictEqual(body, {"jsonrpc": "2.0", "id": 7, "error": {"code": -32600, "message": "Invalid request"}});
490
+ done();
491
+ });
492
+ });
493
+ });
494
+ });
495
+
496
+ it("a notification is accepted with no answer", (done) => {
497
+ rpc({body: JSON.stringify({jsonrpc: "2.0", method: "notifications/initialized"})}, (status, headers, body) => {
498
+ assert.strictEqual(status, 202);
499
+ assert.strictEqual(body, null);
500
+ done();
501
+ });
502
+ });
503
+
504
+ it("too large", (done) => {
505
+ rpc({body: "x".repeat(1024 * 1024 + 1)}, (status) => {
506
+ assert.strictEqual(status, 413);
507
+ done();
508
+ });
509
+ });
510
+
511
+ it("initialize: the client's protocol version when supported, else the latest", (done) => {
512
+ rpc({body: message({method: "initialize", params: {protocolVersion: "2025-03-26"}})}, (status, headers, body) => {
513
+ assert.strictEqual(status, 200);
514
+ assert.strictEqual(headers["Content-Type"], "application/json");
515
+ assert.strictEqual(body.result.protocolVersion, "2025-03-26");
516
+ assert.deepStrictEqual(body.result.capabilities, {"tools": {"listChanged": false}});
517
+ assert.strictEqual(body.result.serverInfo.name, "soajs-gateway");
518
+ rpc({body: message({method: "initialize", params: {protocolVersion: "1999-01-01"}})}, (status, headers, body) => {
519
+ assert.strictEqual(body.result.protocolVersion, "2025-06-18");
520
+ done();
521
+ });
522
+ });
523
+ });
524
+
525
+ it("ping and an unknown method", (done) => {
526
+ rpc({body: message({method: "ping"})}, (status, headers, body) => {
527
+ assert.deepStrictEqual(body, {"jsonrpc": "2.0", "id": 1, "result": {}});
528
+ rpc({body: message({method: "resources/list"})}, (status, headers, body) => {
529
+ assert.strictEqual(body.error.code, -32601);
530
+ done();
531
+ });
532
+ });
533
+ });
534
+
535
+ it("tools/list", (done) => {
536
+ rpc({body: message({method: "tools/list"})}, (status, headers, body) => {
537
+ assert.deepStrictEqual(body.result.tools.map((t) => t.name), ["urac_get_users", "urac_post_user_id", "urac_delete_user_id"]);
538
+ done();
539
+ });
540
+ });
541
+
542
+ it("tools/call: no name, an unknown tool, a call", (done) => {
543
+ rpc({body: message({method: "tools/call", params: {}})}, (status, headers, body) => {
544
+ assert.strictEqual(body.error.code, -32602);
545
+ rpc({body: message({method: "tools/call", params: {name: "urac_nope"}})}, (status, headers, body) => {
546
+ assert.deepStrictEqual(body.error, {"code": -32602, "message": "Unknown tool: urac_nope"});
547
+ rpc({gatewayPort: 1, body: message({method: "tools/call", params: {name: "urac_get_users", arguments: {}}})}, (status, headers, body) => {
548
+ assert.strictEqual(body.result.isError, true);
549
+ done();
550
+ });
551
+ });
552
+ });
553
+ });
554
+ });
555
+ });
556
+
557
+ describe("acl lib", () => {
558
+ it("grouped and plain restricted ACLs list their apis", () => {
559
+ let req = makeReq({
560
+ acl: {
561
+ urac: {
562
+ "1": {access: true, apisPermission: "restricted", get: [{group: "a", apis: {"/x": {}}}, {group: "b", apis: {"/y": {}, "/x": {}}}]},
563
+ "2": {access: true, apisPermission: "restricted", post: {apis: {"/z": {}}}}
564
+ }
565
+ }
566
+ });
567
+ assert.deepStrictEqual(aclLib.computeACL(req).finalACL, {"urac": {"version": "2", "post": ["/z"]}});
568
+ req = makeReq({acl: {urac: {"1": {access: true, apisPermission: "restricted", get: [{group: "a", apis: {"/x": {}}}, {group: "b", apis: {"/y": {}, "/x": {}}}]}}}});
569
+ assert.deepStrictEqual(aclLib.computeACL(req).finalACL, {"urac": {"version": "1", "get": ["/x", "/y"]}});
570
+ });
571
+ });
572
+ });