soajs.controller 4.3.21 → 4.3.23

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.
@@ -78,6 +78,35 @@ function startServer(serverConfig, callback) {
78
78
  mReply.service.route = '/heartbeat';
79
79
  res.json(mReply)
80
80
  });
81
+ // what a soajs service answers on GET /mcp/tools, for the /soajs/mcp tests (UC8)
82
+ mApp.get('/mcp/tools', (req, res) => {
83
+ res.json({
84
+ 'result': true,
85
+ 'ts': Date.now(),
86
+ 'service': {'service': serverConfig.name.toUpperCase(), 'type': 'rest', 'route': '/mcp/tools'},
87
+ 'data': {
88
+ 'name': serverConfig.name,
89
+ 'version': '1',
90
+ 'tools': [
91
+ {
92
+ 'm': 'get', 'v': '/myroute', 'l': 'Route to test get', 'group': 'Integration test',
93
+ 'inputSchema': {'type': 'object', 'properties': {'name': {'type': 'string'}}, 'required': ['name']},
94
+ 'fields': {'name': {'in': 'query', 'path': ['name']}}
95
+ },
96
+ {
97
+ 'm': 'post', 'v': '/myroute', 'l': 'Route to test post',
98
+ 'inputSchema': {'type': 'object', 'properties': {'title': {'type': 'string'}}},
99
+ 'fields': {'title': {'in': 'body', 'path': ['title']}}
100
+ },
101
+ {
102
+ 'm': 'delete', 'v': '/myroute', 'l': 'Route to test delete',
103
+ 'inputSchema': {'type': 'object', 'properties': {}},
104
+ 'fields': {}
105
+ }
106
+ ]
107
+ }
108
+ });
109
+ });
81
110
  mApp.get('/wrench', (req, res) => {
82
111
  mReply.service.route = '/wrench';
83
112
  res.json(mReply)
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+
3
+ const assert = require('assert');
4
+ const request = require('request');
5
+ const helper = require("../../helper.js");
6
+ let Controller = helper.requireModule('./server/controller');
7
+ let localConfig = helper.requireModule('./config.js');
8
+
9
+ let mock = require('../UC2-http-methods/service-mock/index.js');
10
+
11
+ //NOTE: the mock service answers GET /mcp/tools on its maintenance port with tools for /myroute;
12
+ // the gateway custom registry has mcp.enabled
13
+ describe("Integration for Usecase 8 - MCP endpoint /soajs/mcp", function () {
14
+ let extKey = "3d90163cf9d6b3076ad26aa5ed58556348069258e5c6c941ee0f18448b570ad1c5c790e2d2a1989680c55f4904e2005ff5f8e71606e4aa641e67882f4210ebbc5460ff305dcb36e6ec2a2299cf0448ef60b9e38f41950ec251c1cf41f05f3ce9";
15
+ let ownerToken = "cfb209a91b23896820f510aadbf1f4284b512123";
16
+
17
+ let c = new Controller({
18
+ "serviceName": localConfig.serviceName,
19
+ "serviceVersion": localConfig.serviceVersion,
20
+ "serviceGroup": localConfig.serviceGroup,
21
+ "servicePort": localConfig.servicePort,
22
+ "maintenance": localConfig.maintenance
23
+ });
24
+ let registry = null;
25
+ let log = null;
26
+ let service = null;
27
+ let server = null;
28
+ let serverMaintenance = null;
29
+ let httpmethods = null;
30
+
31
+ function mcp(body, opts, cb) {
32
+ opts = opts || {};
33
+ let headers = {key: extKey};
34
+ if (opts.token !== false) {
35
+ headers.authorization = "Bearer " + (opts.token || ownerToken);
36
+ }
37
+ request({
38
+ "method": opts.method || "POST",
39
+ "uri": 'http://127.0.0.1:' + localConfig.servicePort + '/soajs/mcp',
40
+ "headers": headers,
41
+ "body": (typeof body === "string") ? body : JSON.stringify(body)
42
+ }, (error, response, text) => {
43
+ assert.ifError(error);
44
+ let json = null;
45
+ try {
46
+ json = text ? JSON.parse(text) : null;
47
+ } catch (e) {
48
+ json = text;
49
+ }
50
+ return cb(response.statusCode, json);
51
+ });
52
+ }
53
+
54
+ before((done) => {
55
+ mock.startServer(null, function (servers) {
56
+ httpmethods = servers;
57
+ });
58
+ c.init((_registry, _log, _service, _server, _serverMaintenance) => {
59
+ registry = _registry;
60
+ log = _log;
61
+ service = _service;
62
+ server = _server;
63
+ serverMaintenance = _serverMaintenance;
64
+ c.start(registry, log, service, server, serverMaintenance, () => {
65
+ setTimeout(function () {
66
+ done();
67
+ }, 5000);
68
+ });
69
+ });
70
+ });
71
+
72
+ it("initialize", function (done) {
73
+ mcp({jsonrpc: "2.0", id: 1, method: "initialize", params: {protocolVersion: "2025-06-18", capabilities: {}, clientInfo: {name: "test", version: "1"}}}, {}, (status, body) => {
74
+ assert.strictEqual(status, 200);
75
+ assert.strictEqual(body.id, 1);
76
+ assert.strictEqual(body.result.protocolVersion, "2025-06-18");
77
+ assert.deepStrictEqual(body.result.capabilities, {"tools": {"listChanged": false}});
78
+ assert.strictEqual(body.result.serverInfo.name, "soajs-gateway");
79
+ done();
80
+ });
81
+ });
82
+
83
+ it("notifications/initialized is accepted", function (done) {
84
+ mcp({jsonrpc: "2.0", method: "notifications/initialized"}, {}, (status, body) => {
85
+ assert.strictEqual(status, 202);
86
+ assert.strictEqual(body, null);
87
+ done();
88
+ });
89
+ });
90
+
91
+ it("tools/list has the tools of the services the caller may use", function (done) {
92
+ mcp({jsonrpc: "2.0", id: 2, method: "tools/list"}, {}, (status, body) => {
93
+ assert.strictEqual(status, 200);
94
+ let names = body.result.tools.map((t) => t.name);
95
+ assert.ok(names.indexOf("httpmethods_get_myroute") !== -1, JSON.stringify(names));
96
+ assert.ok(names.indexOf("httpmethods_post_myroute") !== -1);
97
+ assert.ok(names.indexOf("httpmethods_delete_myroute") !== -1);
98
+ let get = body.result.tools.find((t) => t.name === "httpmethods_get_myroute");
99
+ assert.deepStrictEqual(get.inputSchema, {"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]});
100
+ assert.strictEqual(get.annotations.readOnlyHint, true);
101
+ done();
102
+ });
103
+ });
104
+
105
+ it("tools/call runs the api through the gateway", function (done) {
106
+ mcp({jsonrpc: "2.0", id: 3, method: "tools/call", params: {name: "httpmethods_get_myroute", arguments: {name: "ann"}}}, {}, (status, body) => {
107
+ assert.strictEqual(status, 200);
108
+ assert.strictEqual(body.result.isError, false, JSON.stringify(body));
109
+ assert.strictEqual(body.result.structuredContent.method, "GET");
110
+ assert.strictEqual(body.result.structuredContent.url, "/myroute?name=ann");
111
+ assert.strictEqual(body.result.structuredContent.firstname, "antoine");
112
+ mcp({jsonrpc: "2.0", id: 4, method: "tools/call", params: {name: "httpmethods_post_myroute", arguments: {title: "t"}}}, {}, (status, body) => {
113
+ assert.strictEqual(body.result.isError, false, JSON.stringify(body));
114
+ assert.strictEqual(body.result.structuredContent.method, "POST");
115
+ done();
116
+ });
117
+ });
118
+ });
119
+
120
+ it("tools/call of an unknown tool", function (done) {
121
+ mcp({jsonrpc: "2.0", id: 5, method: "tools/call", params: {name: "httpmethods_get_nope", arguments: {}}}, {}, (status, body) => {
122
+ assert.deepStrictEqual(body.error, {"code": -32602, "message": "Unknown tool: httpmethods_get_nope"});
123
+ done();
124
+ });
125
+ });
126
+
127
+ it("no signed-in user is refused, like /soajs/acl", function (done) {
128
+ mcp({jsonrpc: "2.0", id: 6, method: "tools/list"}, {token: false}, (status, body) => {
129
+ assert.strictEqual(body.result, false);
130
+ assert.ok(body.errors, JSON.stringify(body));
131
+ done();
132
+ });
133
+ });
134
+
135
+ it("GET is not allowed", function (done) {
136
+ mcp("", {method: "GET"}, (status, body) => {
137
+ assert.strictEqual(status, 405);
138
+ assert.strictEqual(body.error.code, -32600);
139
+ done();
140
+ });
141
+ });
142
+
143
+ it("a body that is not JSON", function (done) {
144
+ mcp("{nope", {}, (status, body) => {
145
+ assert.strictEqual(status, 400);
146
+ assert.strictEqual(body.error.code, -32700);
147
+ done();
148
+ });
149
+ });
150
+
151
+ after((done) => {
152
+ mock.killServer(httpmethods);
153
+ c.stop(registry, log, service, server, serverMaintenance, () => {
154
+ done();
155
+ });
156
+ });
157
+ });
@@ -48,6 +48,10 @@ describe("starting integration tests", () => {
48
48
  // to test cache invalidation with the mongo model
49
49
  require("./UC7-cache-invalidate/index.js");
50
50
 
51
+
52
+ // to test the MCP endpoint /soajs/mcp
53
+ require("./UC8-mcp/index.js");
54
+
51
55
  done();
52
56
  });
53
57
  });
@@ -36,6 +36,8 @@ describe("Starting Gateway Unit test", () => {
36
36
  require("./mw/oauth/index.js");
37
37
  require("./mw/response/index.js");
38
38
  require("./mw/soajs/index.js");
39
+ require("./mw/soajsRoutes/keyACL.js");
40
+ require("./mw/soajsRoutes/mcp.js");
39
41
  require("./mw/traffic/index.js");
40
42
  require("./mw/idempotency/index.js");
41
43
  require("./mw/idempotency/memory.js");
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+
3
+ const helper = require("../../../helper.js");
4
+ const mw = helper.requireModule('./mw/soajsRoutes/keyACL/index');
5
+ const assert = require('assert');
6
+
7
+ let configuration = {
8
+ core: {
9
+ error: {
10
+ getError: (code) => {
11
+ return {code: code};
12
+ }
13
+ }
14
+ },
15
+ serviceName: "controller",
16
+ serviceVersion: "1"
17
+ };
18
+
19
+ let getACL = (acl, cb) => {
20
+ let req = {
21
+ headers: {key: "extKey"},
22
+ soajs: {
23
+ registry: {services: {controller: {}}},
24
+ controller: {
25
+ serviceParams: {
26
+ keyObj: {
27
+ tenant: {id: "t1", name: "Tenant", code: "TNT", type: "product"},
28
+ application: {package: "PROD_PCK", acl: acl}
29
+ },
30
+ packObj: {}
31
+ }
32
+ },
33
+ log: {
34
+ debug: () => {
35
+ }
36
+ },
37
+ controllerResponse: (response) => {
38
+ return cb(response);
39
+ }
40
+ }
41
+ };
42
+ mw(configuration)(req, {}, () => {
43
+ req.soajs.controller.gotoservice(req);
44
+ });
45
+ };
46
+
47
+ describe("Unit test for: mw - soajsRoutes keyACL", () => {
48
+
49
+ it("Fail - no key", (done) => {
50
+ let req = {
51
+ headers: {},
52
+ soajs: {
53
+ controllerResponse: (response) => {
54
+ assert.deepStrictEqual(response, {code: 132});
55
+ done();
56
+ }
57
+ }
58
+ };
59
+ mw(configuration)(req, {}, () => {
60
+ });
61
+ });
62
+
63
+ it("Success - picks the latest version whatever the order of the versions", (done) => {
64
+ // integer keys are always walked in ascending order, decimal keys in insertion order
65
+ getACL({
66
+ "urac": {
67
+ "2.1": {"access": true},
68
+ "1.5": {"access": true},
69
+ "3.5": {"access": true}
70
+ }
71
+ }, (response) => {
72
+ assert.deepStrictEqual(response.finalACL, {"urac": {"version": "3.5"}});
73
+ assert.deepStrictEqual(response.packages, ["PROD_PCK"]);
74
+ assert.deepStrictEqual(response.tenant, {id: "t1", name: "Tenant", code: "TNT", type: "product"});
75
+ done();
76
+ });
77
+ });
78
+
79
+ it("Success - restricted lists the apis of the latest version per method", (done) => {
80
+ getACL({
81
+ "urac": {
82
+ "2": {
83
+ "access": true,
84
+ "apisPermission": "restricted",
85
+ "get": {"apis": {"/user": {}, "/users": {}}},
86
+ "post": {"apis": {"/user": {}}}
87
+ },
88
+ "1": {
89
+ "access": true,
90
+ "apisPermission": "restricted",
91
+ "delete": {"apis": {"/user": {}}}
92
+ }
93
+ }
94
+ }, (response) => {
95
+ assert.deepStrictEqual(response.finalACL, {
96
+ "urac": {
97
+ "version": "2",
98
+ "get": ["/user", "/users"],
99
+ "post": ["/user"]
100
+ }
101
+ });
102
+ done();
103
+ });
104
+ });
105
+ });