From aa49f8d9d7890eec086dd4ccef9499777d085e23 Mon Sep 17 00:00:00 2001 From: justyy Date: Mon, 26 Jan 2026 22:52:13 +0000 Subject: [PATCH 1/3] Fix --- src/index.js | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 76b694b..3898f40 100755 --- a/src/index.js +++ b/src/index.js @@ -187,6 +187,14 @@ function calculateRPS() { log(`Max Payload Size = ${config.max_payload_size}`); app.use(bodyParser.json({ limit: config.max_payload_size })); // For JSON payloads +app.use((err, req, res, next) => { + if (err instanceof SyntaxError && err.status === 400 && "body" in err) { + log(`Invalid JSON received from ${req.ip}`); + return res.status(400).json({ error: "Invalid JSON" }); + } + next(err); +}); + // Configure rate limiting const limiter = rateLimit({ windowMs: rateLimitConfig.windowMs, // Time window in milliseconds @@ -497,8 +505,8 @@ app.all("/", async (req, res) => { }, ); - if (!result) { - res.status(500).json({ error: "Failed to choose node" }); + if (!result || !result.selected) { + res.status(503).json({ error: "Failed to choose node" }); return; } @@ -587,7 +595,14 @@ app.all("/", async (req, res) => { } else { return res.status(405).json({ error: "Method Not Allowed" }); } - data = JSON.parse(result.data); + try { + data = JSON.parse(result.data); + } catch { + data = { + raw: result.data, + warning: "Upstream did not return JSON", + }; + } if (method === "GET") { data["status_code"] = 200; } From ebcd629660d35ef9d72bac8f56b1243abafa9956 Mon Sep 17 00:00:00 2001 From: justyy Date: Mon, 26 Jan 2026 23:01:31 +0000 Subject: [PATCH 2/3] FIx --- js_tests/functions.test.js | 119 +++++++++++++++++++++++++ src/functions.js | 43 +++++++++ src/index.js | 43 +-------- tests/integration-tests-error-nodes.sh | 2 +- 4 files changed, 165 insertions(+), 42 deletions(-) diff --git a/js_tests/functions.test.js b/js_tests/functions.test.js index 448b433..6bd55b5 100644 --- a/js_tests/functions.test.js +++ b/js_tests/functions.test.js @@ -6,6 +6,8 @@ import { shuffle, log, sleep, + calculateErrorPercentage, + calculatePercentage, } from "../src/functions.js"; describe("secondsToTimeDict", () => { @@ -147,3 +149,120 @@ describe("sleep", () => { expect(Math.ceil(end - start)).toBeGreaterThanOrEqual(100); }); }); + +describe("calculatePercentage", () => { + beforeEach(() => { + // total_counter is used as a global in calculatePercentage + global.total_counter = 100; + }); + + test("calculates percentage correctly for multiple URLs", () => { + const accessCounters = new Map([ + ["/api/a", 30], + ["/api/b", 70], + ]); + + const result = calculatePercentage(accessCounters); + + expect(result).toEqual({ + "/api/a": { percent: 30.0, count: 30 }, + "/api/b": { percent: 70.0, count: 70 }, + }); + }); + + test("rounds percentage to 2 decimal places", () => { + const accessCounters = new Map([["/api/a", 33]]); + + global.total_counter = 99; + + const result = calculatePercentage(accessCounters); + + expect(result["/api/a"].percent).toBe(33.33); + }); + + test("handles zero counts", () => { + const accessCounters = new Map([["/api/a", 0]]); + + const result = calculatePercentage(accessCounters); + + expect(result).toEqual({ + "/api/a": { percent: 0, count: 0 }, + }); + }); +}); + +describe("calculateErrorPercentage", () => { + test("calculates error and success rates correctly", () => { + const errorCounters = new Map([["/api/a", 5]]); + + const accessCounters = new Map([["/api/a", 100]]); + + const result = calculateErrorPercentage(errorCounters, accessCounters); + + expect(result).toEqual({ + "/api/a": { + errRate: 5.0, + total: 100, + errorCount: 5, + succRate: 95.0, + }, + }); + }); + + test("rounds rates to 3 decimal places", () => { + const errorCounters = new Map([["/api/a", 1]]); + + const accessCounters = new Map([["/api/a", 3]]); + + const result = calculateErrorPercentage(errorCounters, accessCounters); + + expect(result["/api/a"].errRate).toBe(33.333); + expect(result["/api/a"].succRate).toBe(66.667); + }); + + test("handles URLs with zero total requests", () => { + const errorCounters = new Map([["/api/a", 10]]); + + const accessCounters = new Map(); // no access data + + const result = calculateErrorPercentage(errorCounters, accessCounters); + + expect(result).toEqual({ + "/api/a": { + errRate: 0, + total: 0, + errorCount: 0, + succRate: 100, + }, + }); + }); + + test("handles multiple URLs independently", () => { + const errorCounters = new Map([ + ["/api/a", 2], + ["/api/b", 1], + ]); + + const accessCounters = new Map([ + ["/api/a", 10], + ["/api/b", 4], + ]); + + const result = calculateErrorPercentage(errorCounters, accessCounters); + + expect(result).toEqual({ + "/api/a": { + errRate: 20.0, + total: 10, + errorCount: 2, + succRate: 80.0, + }, + "/api/b": { + errRate: 25.0, + total: 4, + errorCount: 1, + succRate: 75.0, + }, + }); + }); +}); diff --git a/src/functions.js b/src/functions.js index ddc4468..2865d51 100755 --- a/src/functions.js +++ b/src/functions.js @@ -86,6 +86,47 @@ function isObjectEmptyOrNullOrUndefined(obj) { ); } +function calculatePercentage(accessCounters) { + const percentageDict = {}; + + for (let [url, count] of accessCounters) { + let percentage = (count / total_counter) * 100; + percentageDict[url] = { + percent: parseFloat(percentage.toFixed(2)), + count: count, + }; + } + + return percentageDict; +} + +function calculateErrorPercentage(error_counters, access_counters) { + const percentageDict = {}; + + for (let [url, count] of error_counters) { + const totalRequests = access_counters.get(url) || 0; + if (totalRequests > 0) { + let percentage = (count / totalRequests) * 100; + percentageDict[url] = { + errRate: parseFloat(percentage.toFixed(3)), + total: totalRequests, + errorCount: count, + succRate: parseFloat((100 - percentage).toFixed(3)), + }; + } else { + // If there are no requests, set rates to zero + percentageDict[url] = { + errRate: 0, + total: 0, + errorCount: 0, + succRate: 100, + }; + } + } + + return percentageDict; +} + export { shuffle, log, @@ -94,4 +135,6 @@ export { secondsToTimeDict, sleep, isObjectEmptyOrNullOrUndefined, + calculatePercentage, + calculateErrorPercentage, }; diff --git a/src/index.js b/src/index.js index 3898f40..04e2ba2 100755 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,8 @@ import { limitStringMaxLength, secondsToTimeDict, isObjectEmptyOrNullOrUndefined, + calculateErrorPercentage, + calculatePercentage, } from "./functions.js"; import { @@ -413,47 +415,6 @@ async function getServerData(server) { } } -function calculatePercentage(accessCounters) { - const percentageDict = {}; - - for (let [url, count] of accessCounters) { - let percentage = (count / total_counter) * 100; - percentageDict[url] = { - percent: parseFloat(percentage.toFixed(2)), - count: count, - }; - } - - return percentageDict; -} - -function calculateErrorPercentage(error_counters, access_counters) { - const percentageDict = {}; - - for (let [url, count] of error_counters) { - const totalRequests = access_counters.get(url) || 0; - if (totalRequests > 0) { - let percentage = (count / totalRequests) * 100; - percentageDict[url] = { - errRate: parseFloat(percentage.toFixed(3)), - total: totalRequests, - errorCount: count, - succRate: parseFloat((100 - percentage).toFixed(3)), - }; - } else { - // If there are no requests, set rates to zero - percentageDict[url] = { - errRate: 0, - total: 0, - errorCount: 0, - succRate: 100, - }; - } - } - - return percentageDict; -} - // Handle incoming requests app.all("/", async (req, res) => { const ip = req.ip || req.headers["x-forwarded-for"] || "Unknown IP"; diff --git a/tests/integration-tests-error-nodes.sh b/tests/integration-tests-error-nodes.sh index 6d54c71..a7a12f8 100755 --- a/tests/integration-tests-error-nodes.sh +++ b/tests/integration-tests-error-nodes.sh @@ -61,7 +61,7 @@ send_a_get_request_header_when_all_nodes_are_down() { ## curl -s https://api.steemyy.com | jq resp_status_code=$(curl -m 5 -o /dev/null -s -w "%{http_code}\n" http://127.0.0.1:443/) - if [ "$resp_status_code" != "500" ]; then + if [ "$resp_status_code" != "503" ]; then echo "send_a_get_request_header failed with http response code: $resp_status_code" return 1 fi From c006e757fc010db94f959373b46d5fdde4f35399 Mon Sep 17 00:00:00 2001 From: justyy Date: Mon, 26 Jan 2026 23:06:31 +0000 Subject: [PATCH 3/3] Fix --- js_tests/functions.test.js | 54 ++++++++++++++++++++++++++++++-------- src/functions.js | 5 ++-- src/index.js | 2 +- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/js_tests/functions.test.js b/js_tests/functions.test.js index 6bd55b5..059d3c0 100644 --- a/js_tests/functions.test.js +++ b/js_tests/functions.test.js @@ -151,18 +151,15 @@ describe("sleep", () => { }); describe("calculatePercentage", () => { - beforeEach(() => { - // total_counter is used as a global in calculatePercentage - global.total_counter = 100; - }); - test("calculates percentage correctly for multiple URLs", () => { const accessCounters = new Map([ ["/api/a", 30], ["/api/b", 70], ]); - const result = calculatePercentage(accessCounters); + const totalCounter = 100; + + const result = calculatePercentage(accessCounters, totalCounter); expect(result).toEqual({ "/api/a": { percent: 30.0, count: 30 }, @@ -173,22 +170,57 @@ describe("calculatePercentage", () => { test("rounds percentage to 2 decimal places", () => { const accessCounters = new Map([["/api/a", 33]]); - global.total_counter = 99; + const totalCounter = 99; - const result = calculatePercentage(accessCounters); + const result = calculatePercentage(accessCounters, totalCounter); - expect(result["/api/a"].percent).toBe(33.33); + expect(result).toEqual({ + "/api/a": { percent: 33.33, count: 33 }, + }); }); - test("handles zero counts", () => { + test("handles zero count correctly", () => { const accessCounters = new Map([["/api/a", 0]]); - const result = calculatePercentage(accessCounters); + const totalCounter = 50; + + const result = calculatePercentage(accessCounters, totalCounter); expect(result).toEqual({ "/api/a": { percent: 0, count: 0 }, }); }); + + test("handles empty accessCounters", () => { + const accessCounters = new Map(); + const totalCounter = 100; + + const result = calculatePercentage(accessCounters, totalCounter); + + expect(result).toEqual({}); + }); + + test("handles total_counter smaller than individual count", () => { + const accessCounters = new Map([["/api/a", 150]]); + + const totalCounter = 100; + + const result = calculatePercentage(accessCounters, totalCounter); + + expect(result).toEqual({ + "/api/a": { percent: 150.0, count: 150 }, + }); + }); + + test("handles total_counter = 0 safely", () => { + const accessCounters = new Map([["/api/a", 10]]); + + const result = calculatePercentage(accessCounters, 0); + + expect(result).toEqual({ + "/api/a": { percent: 0, count: 10 }, + }); + }); }); describe("calculateErrorPercentage", () => { diff --git a/src/functions.js b/src/functions.js index 2865d51..ff47893 100755 --- a/src/functions.js +++ b/src/functions.js @@ -86,11 +86,12 @@ function isObjectEmptyOrNullOrUndefined(obj) { ); } -function calculatePercentage(accessCounters) { +function calculatePercentage(accessCounters, total_counter) { const percentageDict = {}; for (let [url, count] of accessCounters) { - let percentage = (count / total_counter) * 100; + let percentage = total_counter > 0 ? (count / total_counter) * 100 : 0; + percentageDict[url] = { percent: parseFloat(percentage.toFixed(2)), count: count, diff --git a/src/index.js b/src/index.js index 04e2ba2..2bd91e7 100755 --- a/src/index.js +++ b/src/index.js @@ -629,7 +629,7 @@ app.all("/", async (req, res) => { month: diff.months, year: diff.years, }, - access_counters: calculatePercentage(access_counters), + access_counters: calculatePercentage(access_counters, total_counter), error_counters: calculateErrorPercentage(error_counters, access_counters), not_chosen_counters: Object.fromEntries(not_chosen_counters), jussi_behind_counters: Object.fromEntries(jussi_behind_counters),