Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions js_tests/functions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
shuffle,
log,
sleep,
calculateErrorPercentage,
calculatePercentage,
} from "../src/functions.js";

describe("secondsToTimeDict", () => {
Expand Down Expand Up @@ -147,3 +149,152 @@ describe("sleep", () => {
expect(Math.ceil(end - start)).toBeGreaterThanOrEqual(100);
});
});

describe("calculatePercentage", () => {
test("calculates percentage correctly for multiple URLs", () => {
const accessCounters = new Map([
["/api/a", 30],
["/api/b", 70],
]);

const totalCounter = 100;

const result = calculatePercentage(accessCounters, totalCounter);

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]]);

const totalCounter = 99;

const result = calculatePercentage(accessCounters, totalCounter);

expect(result).toEqual({
"/api/a": { percent: 33.33, count: 33 },
});
});

test("handles zero count correctly", () => {
const accessCounters = new Map([["/api/a", 0]]);

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", () => {
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,
},
});
});
});
44 changes: 44 additions & 0 deletions src/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,48 @@ function isObjectEmptyOrNullOrUndefined(obj) {
);
}

function calculatePercentage(accessCounters, total_counter) {
const percentageDict = {};

for (let [url, count] of accessCounters) {
let percentage = total_counter > 0 ? (count / total_counter) * 100 : 0;

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,
Expand All @@ -94,4 +136,6 @@ export {
secondsToTimeDict,
sleep,
isObjectEmptyOrNullOrUndefined,
calculatePercentage,
calculateErrorPercentage,
};
66 changes: 21 additions & 45 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
limitStringMaxLength,
secondsToTimeDict,
isObjectEmptyOrNullOrUndefined,
calculateErrorPercentage,
calculatePercentage,
} from "./functions.js";

import {
Expand Down Expand Up @@ -187,6 +189,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
Expand Down Expand Up @@ -405,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";
Expand Down Expand Up @@ -497,8 +466,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;
}

Expand Down Expand Up @@ -587,7 +556,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;
}
Expand Down Expand Up @@ -653,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),
Expand Down
2 changes: 1 addition & 1 deletion tests/integration-tests-error-nodes.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading