soajs.controller 4.3.1 → 4.3.3

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.
@@ -6,9 +6,14 @@ on:
6
6
  pull_request:
7
7
  branches: [ master, develop ]
8
8
 
9
+ env:
10
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
11
+
9
12
  jobs:
10
13
  test:
11
14
  runs-on: ubuntu-latest
15
+ env:
16
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
12
17
 
13
18
  services:
14
19
  mongodb:
@@ -18,10 +23,10 @@ jobs:
18
23
 
19
24
  steps:
20
25
  - name: Checkout code
21
- uses: actions/checkout@v4
26
+ uses: actions/checkout@v4.2.2
22
27
 
23
28
  - name: Setup Node.js
24
- uses: actions/setup-node@v4
29
+ uses: actions/setup-node@v4.4.0
25
30
  with:
26
31
  node-version: '24'
27
32
 
@@ -6,9 +6,14 @@ on:
6
6
  - release
7
7
  - 'release/**'
8
8
 
9
+ env:
10
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
11
+
9
12
  jobs:
10
13
  test:
11
14
  runs-on: ubuntu-latest
15
+ env:
16
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
12
17
 
13
18
  services:
14
19
  mongodb:
@@ -18,10 +23,10 @@ jobs:
18
23
 
19
24
  steps:
20
25
  - name: Checkout code
21
- uses: actions/checkout@v4
26
+ uses: actions/checkout@v4.2.2
22
27
 
23
28
  - name: Setup Node.js
24
- uses: actions/setup-node@v4
29
+ uses: actions/setup-node@v4.4.0
25
30
  with:
26
31
  node-version: '24'
27
32
 
@@ -40,10 +45,12 @@ jobs:
40
45
  publish-docker:
41
46
  runs-on: ubuntu-latest
42
47
  needs: test
48
+ env:
49
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
43
50
 
44
51
  steps:
45
52
  - name: Checkout code
46
- uses: actions/checkout@v4
53
+ uses: actions/checkout@v4.2.2
47
54
 
48
55
  - name: Extract version from package.json
49
56
  id: version
@@ -85,7 +92,7 @@ jobs:
85
92
  run: git push origin ${{ steps.version.outputs.PACKAGE_VERSION }} || true
86
93
 
87
94
  - name: Create GitHub Release
88
- uses: softprops/action-gh-release@v1
95
+ uses: softprops/action-gh-release@v2
89
96
  with:
90
97
  tag_name: ${{ steps.version.outputs.PACKAGE_VERSION }}
91
98
  name: Release ${{ steps.version.outputs.PACKAGE_VERSION }}
package/lib/parseURL.js CHANGED
@@ -8,7 +8,20 @@
8
8
  * found in the LICENSE file at the root of this repository
9
9
  */
10
10
 
11
- const url = require('url');
11
+ /**
12
+ * Parse URL using WHATWG URL API and return object compatible with legacy url.parse()
13
+ * @param {string} urlString - URL string to parse
14
+ * @returns {Object} - Object with pathname, path, and query properties
15
+ */
16
+ const parseURL = (urlString) => {
17
+ const urlObj = new URL(urlString, 'http://localhost');
18
+ const search = urlObj.search || '';
19
+ return {
20
+ pathname: urlObj.pathname,
21
+ path: urlObj.pathname + search,
22
+ query: Object.fromEntries(urlObj.searchParams)
23
+ };
24
+ };
12
25
 
13
26
 
14
27
  module.exports = (uri, parsedUrl) => {
@@ -16,7 +29,7 @@ module.exports = (uri, parsedUrl) => {
16
29
  let service_nv = serviceInfo[1];
17
30
  let service_n = service_nv;
18
31
  let service_v = null;
19
-
32
+
20
33
  //check if there is /v1 or v1.1 in the url
21
34
  let matches = uri.match(/\/v[0-9]+(.[0-9]+)?\//);
22
35
  if (matches && matches.length > 0) {
@@ -25,7 +38,7 @@ module.exports = (uri, parsedUrl) => {
25
38
  service_v = hit.replace("v", '');
26
39
  serviceInfo.splice(2, 1);
27
40
  uri = uri.replace(matches[0], "/");
28
- parsedUrl = url.parse(uri, true);
41
+ parsedUrl = parseURL(uri);
29
42
  }
30
43
  }
31
44
 
@@ -12,6 +12,121 @@ const get = (p, o) => p.reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, o);
12
12
 
13
13
  const { httpRequestLight, proxyRequestMonitor } = require("../../lib/request.js");
14
14
 
15
+ // Monitor buffer for batch uploads
16
+ let monitorBuffer = [];
17
+ let monitorFlushTimer = null;
18
+ let monitorFlushInProgress = false;
19
+ let lastReqForFlush = null;
20
+
21
+ /**
22
+ * Flush the monitor buffer to soamonitor
23
+ */
24
+ function flushMonitorBuffer() {
25
+ if (monitorFlushInProgress || monitorBuffer.length === 0 || !lastReqForFlush) {
26
+ return;
27
+ }
28
+
29
+ monitorFlushInProgress = true;
30
+
31
+ // Clear timer if exists
32
+ if (monitorFlushTimer) {
33
+ clearTimeout(monitorFlushTimer);
34
+ monitorFlushTimer = null;
35
+ }
36
+
37
+ // Take current buffer and reset
38
+ let itemsToSend = monitorBuffer;
39
+ let req = lastReqForFlush;
40
+ monitorBuffer = [];
41
+
42
+ let soamonitor = "soamonitor";
43
+ let soamonitorVersion = "1";
44
+ let port = get(["registry", "services", "soamonitor", "port"], req.soajs) || "4050";
45
+ let api = "/monitor/items"; // Batch endpoint
46
+
47
+ req.soajs.awareness.getHost(soamonitor, soamonitorVersion, function (host) {
48
+ monitorFlushInProgress = false;
49
+
50
+ if (!host) {
51
+ req.soajs.log.debug('Monitor buffer flush: Unable to find host for ' + soamonitor);
52
+ // Put items back in buffer for retry
53
+ monitorBuffer = itemsToSend.concat(monitorBuffer);
54
+ return;
55
+ }
56
+
57
+ // Convert body/response to strings
58
+ itemsToSend.forEach(doc => {
59
+ if (doc.body) {
60
+ doc.body = doc.body.toString();
61
+ }
62
+ if (doc.response) {
63
+ doc.response = doc.response.toString();
64
+ }
65
+ });
66
+
67
+ let uri = "http://" + host + ":" + port + api;
68
+ let options = {
69
+ "method": "POST",
70
+ "uri": uri,
71
+ "data": { "items": itemsToSend },
72
+ "json": true
73
+ };
74
+
75
+ httpRequestLight(options)
76
+ .then((body) => {
77
+ if (body && !body.result) {
78
+ req.soajs.log.debug('Monitor buffer flush response:', body);
79
+ } else {
80
+ req.soajs.log.debug('Monitor buffer flushed: ' + itemsToSend.length + ' items');
81
+ }
82
+ })
83
+ .catch((error) => {
84
+ req.soajs.log.debug('Monitor buffer flush failed: ' + error.message);
85
+ });
86
+ });
87
+ }
88
+
89
+ /**
90
+ * Add item to monitor buffer and trigger flush if needed
91
+ * @param {Object} req - Request object
92
+ * @param {Object} doc - Monitor document to buffer
93
+ * @param {Object} monitor - Monitor configuration
94
+ */
95
+ function bufferMonitorItem(req, doc, monitor) {
96
+ monitorBuffer.push(doc);
97
+ lastReqForFlush = req;
98
+
99
+ let bufferConfig = monitor.buffer || {};
100
+ let bufferTime = bufferConfig.time;
101
+ let bufferLimit = bufferConfig.limit;
102
+
103
+ // Check count threshold
104
+ if (bufferLimit && monitorBuffer.length >= bufferLimit) {
105
+ flushMonitorBuffer();
106
+ return;
107
+ }
108
+
109
+ // Start timer if not already running and time threshold is set
110
+ if (bufferTime && !monitorFlushTimer) {
111
+ monitorFlushTimer = setTimeout(() => {
112
+ monitorFlushTimer = null;
113
+ flushMonitorBuffer();
114
+ }, bufferTime);
115
+ }
116
+ }
117
+
118
+ // Graceful shutdown - flush remaining buffer
119
+ process.on('SIGTERM', () => {
120
+ if (monitorBuffer.length > 0 && !monitorFlushInProgress) {
121
+ // Best-effort cleanup
122
+ monitorBuffer = [];
123
+ if (monitorFlushTimer) {
124
+ clearTimeout(monitorFlushTimer);
125
+ monitorFlushTimer = null;
126
+ }
127
+ }
128
+ });
129
+
15
130
  module.exports = (configuration) => {
16
131
 
17
132
  let core = configuration.core;
@@ -26,12 +141,21 @@ module.exports = (configuration) => {
26
141
  */
27
142
  return (req, res) => {
28
143
  preRedirect(req, res, core, function (obj) {
144
+ let monitor = get(["registry", "custom", "gateway", "value", "gotoService", "monitor"], req.soajs);
29
145
  let log_monitor_triggered = false;
30
146
  let log_monitor = (doc) => {
31
147
  if (log_monitor_triggered) {
32
148
  return;
33
149
  }
34
150
  log_monitor_triggered = true;
151
+
152
+ // Check if buffering is enabled
153
+ if (monitor && monitor.buffer && (monitor.buffer.time || monitor.buffer.limit)) {
154
+ bufferMonitorItem(req, doc, monitor);
155
+ return;
156
+ }
157
+
158
+ // Original immediate send logic (unchanged)
35
159
  let soamonitor = "soamonitor";
36
160
  let soamonitorVersion = "1";
37
161
  let port = get(["registry", "services", "soamonitor", "port"], req.soajs);
@@ -72,8 +196,6 @@ module.exports = (configuration) => {
72
196
  }
73
197
  });
74
198
  };
75
-
76
- let monitor = get(["registry", "custom", "gateway", "value", "gotoService", "monitor"], req.soajs);
77
199
  let monitoObj = {
78
200
  "time": {}
79
201
  };
@@ -52,11 +52,12 @@ function getRouteRegExp(route) {
52
52
  * Check if the current request should trigger lastSeen
53
53
  * @param {Object} include - The include whitelist configuration
54
54
  * @param {string} serviceName - The current service name
55
- * @param {string} apiPath - The current API path
55
+ * @param {string} apiPath - The current API path (relative to service, e.g., "/active")
56
+ * @param {string} fullPath - The full API path (e.g., "/connectspaces/active")
56
57
  * @param {string} method - The HTTP method (GET, POST, etc.)
57
58
  * @returns {boolean} - true if lastSeen should be triggered
58
59
  */
59
- function shouldTriggerLastSeen(include, serviceName, apiPath, method) {
60
+ function shouldTriggerLastSeen(include, serviceName, apiPath, fullPath, method) {
60
61
  // No include filter = trigger for all (backward compatible)
61
62
  if (!include) {
62
63
  return true;
@@ -74,9 +75,17 @@ function shouldTriggerLastSeen(include, serviceName, apiPath, method) {
74
75
  return true;
75
76
  }
76
77
 
77
- // API-level match
78
+ // API-level match - check both relative path and full path for backward compatibility
78
79
  if (typeof serviceConfig === 'object' && serviceConfig.apis) {
79
- return isApiMethodMatch(serviceConfig.apis, apiPath, method);
80
+ // Try relative path first (e.g., "/active")
81
+ if (apiPath && isApiMethodMatch(serviceConfig.apis, apiPath, method)) {
82
+ return true;
83
+ }
84
+ // Fallback to full path for backward compatibility (e.g., "/connectspaces/active")
85
+ if (fullPath && fullPath !== apiPath && isApiMethodMatch(serviceConfig.apis, fullPath, method)) {
86
+ return true;
87
+ }
88
+ return false;
80
89
  }
81
90
 
82
91
  return false;
@@ -150,11 +159,13 @@ module.exports = () => {
150
159
 
151
160
  // Check include filter
152
161
  let currentService = get(["soajs", "controller", "serviceParams", "name"], req);
153
- let currentApi = get(["soajs", "controller", "serviceParams", "parsedUrl", "pathname"], req);
162
+ // Get both relative path (e.g., "/active") and full path (e.g., "/connectspaces/active")
163
+ let currentApi = get(["soajs", "controller", "serviceParams", "path"], req);
164
+ let fullPath = get(["soajs", "controller", "serviceParams", "parsedUrl", "pathname"], req);
154
165
  let currentMethod = req.method;
155
166
 
156
- if (!shouldTriggerLastSeen(lastSeen.include, currentService, currentApi, currentMethod)) {
157
- req.soajs.log.debug("lastSeen skipped for [" + currentService + " " + currentMethod + " " + currentApi + "]");
167
+ if (!shouldTriggerLastSeen(lastSeen.include, currentService, currentApi, fullPath, currentMethod)) {
168
+ req.soajs.log.debug("lastSeen skipped for [" + currentService + " " + currentMethod + " " + (currentApi || fullPath) + "]");
158
169
  return;
159
170
  }
160
171
 
package/mw/url/index.js CHANGED
@@ -8,9 +8,23 @@
8
8
  * found in the LICENSE file at the root of this repository
9
9
  */
10
10
 
11
- const url = require('url');
12
11
  let libParseURL = require('../../lib/parseURL');
13
12
 
13
+ /**
14
+ * Parse URL using WHATWG URL API and return object compatible with legacy url.parse()
15
+ * @param {string} urlString - URL string to parse
16
+ * @returns {Object} - Object with pathname, path, and query properties
17
+ */
18
+ const parseURL = (urlString) => {
19
+ const urlObj = new URL(urlString, 'http://localhost');
20
+ const search = urlObj.search || '';
21
+ return {
22
+ pathname: urlObj.pathname,
23
+ path: urlObj.pathname + search,
24
+ query: Object.fromEntries(urlObj.searchParams)
25
+ };
26
+ };
27
+
14
28
  /**
15
29
  * Assure req param (soajs & soajs.controller)
16
30
  * Parse url and fetch service info (soajs.controller.serviceParams)
@@ -30,7 +44,7 @@ module.exports = (configuration) => {
30
44
  req.soajs.controller = {};
31
45
  }
32
46
 
33
- let parsedUrl = url.parse(req.url, true);
47
+ let parsedUrl = parseURL(req.url);
34
48
  if (!req.query && parsedUrl.query) {
35
49
  req.query = parsedUrl.query;
36
50
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "soajs.controller",
3
3
  "description": "soajs multi tenant API gateway",
4
- "version": "4.3.1",
4
+ "version": "4.3.3",
5
5
  "author": {
6
6
  "name": "soajs team",
7
7
  "email": "team@soajs.org"
@@ -14,8 +14,22 @@ const registryModule = require("./../modules/registry");
14
14
 
15
15
  const httpProxy = require('http-proxy');
16
16
  const http = require('http');
17
- const url = require('url');
18
17
  const soajsLib = require("soajs.core.libs");
18
+
19
+ /**
20
+ * Parse URL using WHATWG URL API and return object compatible with legacy url.parse()
21
+ * @param {string} urlString - URL string to parse
22
+ * @returns {Object} - Object with pathname, path, and query properties
23
+ */
24
+ const parseURL = (urlString) => {
25
+ const urlObj = new URL(urlString, 'http://localhost');
26
+ const search = urlObj.search || '';
27
+ return {
28
+ pathname: urlObj.pathname,
29
+ path: urlObj.pathname + search,
30
+ query: Object.fromEntries(urlObj.searchParams)
31
+ };
32
+ };
19
33
  const soajsUtils = soajsLib.utils;
20
34
 
21
35
  const packageInfo = require("../package.json");
@@ -148,7 +162,7 @@ let Maintenance = (core, log, param, serviceIp, regEnvironment, awareness_mw, so
148
162
  log.error('Internal proxy error: ' + error);
149
163
 
150
164
  res.writeHead(200, {'Content-Type': 'application/json'});
151
- let parsedUrl = url.parse(req.url, true);
165
+ let parsedUrl = parseURL(req.url);
152
166
  let response = maintenanceResponse(parsedUrl, param, '/proxySocket');
153
167
  return res.end(JSON.stringify(response));
154
168
  });
@@ -159,7 +173,7 @@ let Maintenance = (core, log, param, serviceIp, regEnvironment, awareness_mw, so
159
173
  res.writeHead(200, {'Content-Type': 'image/x-icon'});
160
174
  return res.end();
161
175
  }
162
- let parsedUrl = url.parse(req.url, true);
176
+ let parsedUrl = parseURL(req.url);
163
177
 
164
178
  if (parsedUrl.pathname === '/reloadRegistry') {
165
179
  reloadRegistry(parsedUrl, core, log, param, serviceIp, (response) => {
@@ -1,13 +1,27 @@
1
1
  "use strict";
2
2
 
3
- const url = require('url');
4
3
  const helper = require("../../helper.js");
5
4
  const lib = helper.requireModule('./lib/parseURL');
6
5
  const assert = require('assert');
7
6
 
7
+ /**
8
+ * Parse URL using WHATWG URL API and return object compatible with legacy url.parse()
9
+ * @param {string} urlString - URL string to parse
10
+ * @returns {Object} - Object with pathname, path, and query properties
11
+ */
12
+ const parseURL = (urlString) => {
13
+ const urlObj = new URL(urlString, 'http://localhost');
14
+ const search = urlObj.search || '';
15
+ return {
16
+ pathname: urlObj.pathname,
17
+ path: urlObj.pathname + search,
18
+ query: Object.fromEntries(urlObj.searchParams)
19
+ };
20
+ };
21
+
8
22
  describe("Unit test for: lib - parseURL", () => {
9
23
  let uri = '/micro2/v1.1/hello';
10
- let parsedURL = url.parse(uri, true);
24
+ let parsedURL = parseURL(uri);
11
25
  it("Vanilla test with v1.1", (done) => {
12
26
  let response = lib(uri, parsedURL);
13
27
  assert.deepStrictEqual(response.name, 'micro2', "lib.parseURL vanilla test with v1.1 failed for name");
@@ -16,10 +30,10 @@ describe("Unit test for: lib - parseURL", () => {
16
30
  });
17
31
  it("Vanilla test with :1.1", (done) => {
18
32
  uri = '/micro2:1.1/hello';
19
- parsedURL = url.parse(uri, true);
33
+ parsedURL = parseURL(uri);
20
34
  let response = lib(uri, parsedURL);
21
35
  assert.deepStrictEqual(response.name, 'micro2', "lib.parseURL vanilla test with 1:1 failed for name");
22
36
  assert.deepStrictEqual(response.service_v, '1.1', "lib.parseURL vanilla test with 1:1 failed version");
23
37
  done();
24
38
  });
25
- });
39
+ });
@@ -0,0 +1,306 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * @license
5
+ * Copyright SOAJS All Rights Reserved.
6
+ *
7
+ * Use of this source code is governed by an Apache license that can be
8
+ * found in the LICENSE file at the root of this repository
9
+ */
10
+
11
+ const assert = require('assert');
12
+
13
+ describe("Unit test for: mw - gotoService monitor buffer", function () {
14
+
15
+ // We need to test the buffer module functions directly
16
+ // Since they are module-level in redirectToService.js, we'll test the logic patterns
17
+
18
+ describe("Buffer configuration parsing", function () {
19
+
20
+ it("should recognize buffer config with time only", function (done) {
21
+ let monitor = {
22
+ "buffer": {
23
+ "time": 5000
24
+ }
25
+ };
26
+ let hasBuffer = monitor && monitor.buffer && (monitor.buffer.time || monitor.buffer.limit);
27
+ assert.strictEqual(hasBuffer, true);
28
+ done();
29
+ });
30
+
31
+ it("should recognize buffer config with limit only", function (done) {
32
+ let monitor = {
33
+ "buffer": {
34
+ "limit": 100
35
+ }
36
+ };
37
+ let hasBuffer = monitor && monitor.buffer && (monitor.buffer.time || monitor.buffer.limit);
38
+ assert.strictEqual(hasBuffer, true);
39
+ done();
40
+ });
41
+
42
+ it("should recognize buffer config with both time and limit", function (done) {
43
+ let monitor = {
44
+ "buffer": {
45
+ "time": 5000,
46
+ "limit": 100
47
+ }
48
+ };
49
+ let hasBuffer = monitor && monitor.buffer && (monitor.buffer.time || monitor.buffer.limit);
50
+ assert.strictEqual(hasBuffer, true);
51
+ done();
52
+ });
53
+
54
+ it("should not recognize buffer config when buffer is not set", function (done) {
55
+ let monitor = {
56
+ "req_info": true
57
+ };
58
+ let hasBuffer = monitor && monitor.buffer && (monitor.buffer.time || monitor.buffer.limit);
59
+ assert.strictEqual(!!hasBuffer, false);
60
+ done();
61
+ });
62
+
63
+ it("should not recognize buffer config when buffer is empty", function (done) {
64
+ let monitor = {
65
+ "buffer": {}
66
+ };
67
+ let hasBuffer = monitor && monitor.buffer && (monitor.buffer.time || monitor.buffer.limit);
68
+ assert.strictEqual(!!hasBuffer, false);
69
+ done();
70
+ });
71
+
72
+ it("should not recognize buffer when monitor is null", function (done) {
73
+ let monitor = null;
74
+ let hasBuffer = monitor && monitor.buffer && (monitor.buffer.time || monitor.buffer.limit);
75
+ assert.strictEqual(!!hasBuffer, false);
76
+ done();
77
+ });
78
+ });
79
+
80
+ describe("Buffer limit threshold logic", function () {
81
+
82
+ it("should trigger flush when buffer reaches limit", function (done) {
83
+ let monitorBuffer = [];
84
+ let bufferLimit = 3;
85
+
86
+ // Simulate adding items
87
+ for (let i = 0; i < 3; i++) {
88
+ monitorBuffer.push({ name: "test", api: "/test" + i });
89
+ }
90
+
91
+ let shouldFlush = bufferLimit && monitorBuffer.length >= bufferLimit;
92
+ assert.strictEqual(shouldFlush, true);
93
+ done();
94
+ });
95
+
96
+ it("should not trigger flush when buffer is below limit", function (done) {
97
+ let monitorBuffer = [];
98
+ let bufferLimit = 10;
99
+
100
+ // Simulate adding items
101
+ for (let i = 0; i < 5; i++) {
102
+ monitorBuffer.push({ name: "test", api: "/test" + i });
103
+ }
104
+
105
+ let shouldFlush = bufferLimit && monitorBuffer.length >= bufferLimit;
106
+ assert.strictEqual(shouldFlush, false);
107
+ done();
108
+ });
109
+
110
+ it("should not trigger flush when no limit is set", function (done) {
111
+ let monitorBuffer = [];
112
+ let bufferLimit = null;
113
+
114
+ // Simulate adding items
115
+ for (let i = 0; i < 100; i++) {
116
+ monitorBuffer.push({ name: "test", api: "/test" + i });
117
+ }
118
+
119
+ let shouldFlush = bufferLimit && monitorBuffer.length >= bufferLimit;
120
+ assert.strictEqual(shouldFlush, false);
121
+ done();
122
+ });
123
+ });
124
+
125
+ describe("Buffer time threshold logic", function () {
126
+
127
+ it("should start timer when time threshold is set and no timer exists", function (done) {
128
+ let monitorFlushTimer = null;
129
+ let bufferTime = 5000;
130
+
131
+ let shouldStartTimer = bufferTime && !monitorFlushTimer;
132
+ assert.strictEqual(shouldStartTimer, true);
133
+ done();
134
+ });
135
+
136
+ it("should not start timer when timer already exists", function (done) {
137
+ let monitorFlushTimer = setTimeout(() => {}, 5000);
138
+ let bufferTime = 5000;
139
+
140
+ let shouldStartTimer = bufferTime && !monitorFlushTimer;
141
+ assert.strictEqual(shouldStartTimer, false);
142
+
143
+ clearTimeout(monitorFlushTimer);
144
+ done();
145
+ });
146
+
147
+ it("should not start timer when no time threshold is set", function (done) {
148
+ let monitorFlushTimer = null;
149
+ let bufferTime = null;
150
+
151
+ let shouldStartTimer = bufferTime && !monitorFlushTimer;
152
+ assert.strictEqual(!!shouldStartTimer, false);
153
+ done();
154
+ });
155
+ });
156
+
157
+ describe("Buffer flush conditions", function () {
158
+
159
+ it("should not flush when buffer is empty", function (done) {
160
+ let monitorBuffer = [];
161
+ let monitorFlushInProgress = false;
162
+
163
+ let shouldFlush = !(monitorFlushInProgress || monitorBuffer.length === 0);
164
+ assert.strictEqual(shouldFlush, false);
165
+ done();
166
+ });
167
+
168
+ it("should not flush when flush is already in progress", function (done) {
169
+ let monitorBuffer = [{ name: "test" }];
170
+ let monitorFlushInProgress = true;
171
+
172
+ let shouldFlush = !(monitorFlushInProgress || monitorBuffer.length === 0);
173
+ assert.strictEqual(shouldFlush, false);
174
+ done();
175
+ });
176
+
177
+ it("should flush when buffer has items and no flush in progress", function (done) {
178
+ let monitorBuffer = [{ name: "test" }];
179
+ let monitorFlushInProgress = false;
180
+
181
+ let shouldFlush = !(monitorFlushInProgress || monitorBuffer.length === 0);
182
+ assert.strictEqual(shouldFlush, true);
183
+ done();
184
+ });
185
+ });
186
+
187
+ describe("Monitor document transformation", function () {
188
+
189
+ it("should convert body to string before sending", function (done) {
190
+ let doc = {
191
+ name: "test",
192
+ body: Buffer.from("test body")
193
+ };
194
+
195
+ if (doc.body) {
196
+ doc.body = doc.body.toString();
197
+ }
198
+
199
+ assert.strictEqual(typeof doc.body, "string");
200
+ assert.strictEqual(doc.body, "test body");
201
+ done();
202
+ });
203
+
204
+ it("should convert response to string before sending", function (done) {
205
+ let doc = {
206
+ name: "test",
207
+ response: Buffer.from('{"result": true}')
208
+ };
209
+
210
+ if (doc.response) {
211
+ doc.response = doc.response.toString();
212
+ }
213
+
214
+ assert.strictEqual(typeof doc.response, "string");
215
+ assert.strictEqual(doc.response, '{"result": true}');
216
+ done();
217
+ });
218
+
219
+ it("should handle doc without body or response", function (done) {
220
+ let doc = {
221
+ name: "test",
222
+ api: "/test"
223
+ };
224
+
225
+ if (doc.body) {
226
+ doc.body = doc.body.toString();
227
+ }
228
+ if (doc.response) {
229
+ doc.response = doc.response.toString();
230
+ }
231
+
232
+ assert.strictEqual(doc.body, undefined);
233
+ assert.strictEqual(doc.response, undefined);
234
+ done();
235
+ });
236
+ });
237
+
238
+ describe("Backward compatibility", function () {
239
+
240
+ it("should send immediately when no buffer config (backward compatible)", function (done) {
241
+ let monitor = {
242
+ "req_info": true,
243
+ "req_response": true
244
+ };
245
+
246
+ let hasBuffer = monitor && monitor.buffer && (monitor.buffer.time || monitor.buffer.limit);
247
+ let shouldSendImmediately = !hasBuffer;
248
+
249
+ assert.strictEqual(shouldSendImmediately, true);
250
+ done();
251
+ });
252
+
253
+ it("should use buffer when buffer config is present", function (done) {
254
+ let monitor = {
255
+ "req_info": true,
256
+ "req_response": true,
257
+ "buffer": {
258
+ "time": 5000,
259
+ "limit": 100
260
+ }
261
+ };
262
+
263
+ let hasBuffer = monitor && monitor.buffer && (monitor.buffer.time || monitor.buffer.limit);
264
+ let shouldSendImmediately = !hasBuffer;
265
+
266
+ assert.strictEqual(shouldSendImmediately, false);
267
+ done();
268
+ });
269
+ });
270
+
271
+ describe("Buffer retry logic", function () {
272
+
273
+ it("should put items back in buffer when host not found", function (done) {
274
+ let monitorBuffer = [];
275
+ let itemsToSend = [{ name: "test1" }, { name: "test2" }];
276
+ let host = null;
277
+
278
+ // Simulate host not found - items should go back to buffer
279
+ if (!host) {
280
+ monitorBuffer = itemsToSend.concat(monitorBuffer);
281
+ }
282
+
283
+ assert.strictEqual(monitorBuffer.length, 2);
284
+ assert.strictEqual(monitorBuffer[0].name, "test1");
285
+ done();
286
+ });
287
+
288
+ it("should preserve order when putting items back", function (done) {
289
+ let monitorBuffer = [{ name: "new1" }];
290
+ let itemsToSend = [{ name: "old1" }, { name: "old2" }];
291
+ let host = null;
292
+
293
+ // Simulate host not found - items should go back to buffer
294
+ if (!host) {
295
+ monitorBuffer = itemsToSend.concat(monitorBuffer);
296
+ }
297
+
298
+ assert.strictEqual(monitorBuffer.length, 3);
299
+ assert.strictEqual(monitorBuffer[0].name, "old1");
300
+ assert.strictEqual(monitorBuffer[1].name, "old2");
301
+ assert.strictEqual(monitorBuffer[2].name, "new1");
302
+ done();
303
+ });
304
+ });
305
+
306
+ });