chore(tests): using test suites and better test names where possible (#6564)

This commit is contained in:
Frank Elsinga 2026-01-01 17:16:01 +01:00 committed by GitHub
commit 6d01cf0aae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 654 additions and 688 deletions

View File

@ -172,9 +172,9 @@ class DomainExpiry extends BeanModel {
}
/**
* @returns {(Date|null)} Expiry date from RDAP
* @returns {Promise<(Date|null)>} Expiry date from RDAP
*/
getExpiryDate() {
async getExpiryDate() {
return getRdapDomainExpiryDate(this.domain);
}

View File

@ -4,14 +4,26 @@ Documentation: https://nodejs.org/api/test.html
Create a test file in this directory with the name `*.js`.
> [!TIP]
> Writing great tests is hard.
>
> You can make our live much simpler by following this guidance:
> - Use `describe()` to group related tests
> - Use `test()` for individual test cases
> - One test per scenario
> - Use descriptive test names: `function() [behavior] [condition]`
> - Don't prefix with "Test" or "Should"
## Template
```js
const test = require("node:test");
const { describe, test } = require("node:test");
const assert = require("node:assert");
test("Test name", async (t) => {
assert.strictEqual(1, 1);
describe("Feature Name", () => {
test("function() returns expected value when condition is met", () => {
assert.strictEqual(1, 1);
});
});
```

View File

@ -1,46 +1,48 @@
const test = require("node:test");
const { describe, test } = require("node:test");
const assert = require("node:assert");
const { ConditionExpressionGroup, ConditionExpression, LOGICAL } = require("../../../server/monitor-conditions/expression.js");
const { evaluateExpressionGroup, evaluateExpression } = require("../../../server/monitor-conditions/evaluator.js");
test("Test evaluateExpression", async (t) => {
const expr = new ConditionExpression("record", "contains", "mx1.example.com");
assert.strictEqual(true, evaluateExpression(expr, { record: "mx1.example.com" }));
assert.strictEqual(false, evaluateExpression(expr, { record: "mx2.example.com" }));
});
describe("Expression Evaluator", () => {
test("evaluateExpression() returns true when condition matches and false otherwise", () => {
const expr = new ConditionExpression("record", "contains", "mx1.example.com");
assert.strictEqual(true, evaluateExpression(expr, { record: "mx1.example.com" }));
assert.strictEqual(false, evaluateExpression(expr, { record: "mx2.example.com" }));
});
test("Test evaluateExpressionGroup with logical AND", async (t) => {
const group = new ConditionExpressionGroup([
new ConditionExpression("record", "contains", "mx1."),
new ConditionExpression("record", "contains", "example.com", LOGICAL.AND),
]);
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "mx1.example.com" }));
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "mx1." }));
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "example.com" }));
});
test("evaluateExpressionGroup() with AND logic requires all conditions to be true", () => {
const group = new ConditionExpressionGroup([
new ConditionExpression("record", "contains", "mx1."),
new ConditionExpression("record", "contains", "example.com", LOGICAL.AND),
]);
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "mx1.example.com" }));
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "mx1." }));
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "example.com" }));
});
test("Test evaluateExpressionGroup with logical OR", async (t) => {
const group = new ConditionExpressionGroup([
new ConditionExpression("record", "contains", "example.com"),
new ConditionExpression("record", "contains", "example.org", LOGICAL.OR),
]);
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "example.com" }));
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "example.org" }));
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "example.net" }));
});
test("Test evaluateExpressionGroup with nested group", async (t) => {
const group = new ConditionExpressionGroup([
new ConditionExpression("record", "contains", "mx1."),
new ConditionExpressionGroup([
test("evaluateExpressionGroup() with OR logic requires at least one condition to be true", () => {
const group = new ConditionExpressionGroup([
new ConditionExpression("record", "contains", "example.com"),
new ConditionExpression("record", "contains", "example.org", LOGICAL.OR),
]),
]);
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "mx1." }));
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "mx1.example.com" }));
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "mx1.example.org" }));
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "example.com" }));
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "example.org" }));
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "mx1.example.net" }));
]);
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "example.com" }));
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "example.org" }));
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "example.net" }));
});
test("evaluateExpressionGroup() evaluates nested groups correctly", () => {
const group = new ConditionExpressionGroup([
new ConditionExpression("record", "contains", "mx1."),
new ConditionExpressionGroup([
new ConditionExpression("record", "contains", "example.com"),
new ConditionExpression("record", "contains", "example.org", LOGICAL.OR),
]),
]);
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "mx1." }));
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "mx1.example.com" }));
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "mx1.example.org" }));
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "example.com" }));
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "example.org" }));
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "mx1.example.net" }));
});
});

View File

@ -1,108 +1,110 @@
const test = require("node:test");
const { describe, test } = require("node:test");
const assert = require("node:assert");
const { operatorMap, OP_CONTAINS, OP_NOT_CONTAINS, OP_LT, OP_GT, OP_LTE, OP_GTE, OP_STR_EQUALS, OP_STR_NOT_EQUALS, OP_NUM_EQUALS, OP_NUM_NOT_EQUALS, OP_STARTS_WITH, OP_ENDS_WITH, OP_NOT_STARTS_WITH, OP_NOT_ENDS_WITH } = require("../../../server/monitor-conditions/operators.js");
test("Test StringEqualsOperator", async (t) => {
const op = operatorMap.get(OP_STR_EQUALS);
assert.strictEqual(true, op.test("mx1.example.com", "mx1.example.com"));
assert.strictEqual(false, op.test("mx1.example.com", "mx1.example.org"));
assert.strictEqual(false, op.test("1", 1)); // strict equality
});
describe("Expression Operators", () => {
test("StringEqualsOperator returns true for identical strings and false otherwise", () => {
const op = operatorMap.get(OP_STR_EQUALS);
assert.strictEqual(true, op.test("mx1.example.com", "mx1.example.com"));
assert.strictEqual(false, op.test("mx1.example.com", "mx1.example.org"));
assert.strictEqual(false, op.test("1", 1)); // strict equality
});
test("Test StringNotEqualsOperator", async (t) => {
const op = operatorMap.get(OP_STR_NOT_EQUALS);
assert.strictEqual(true, op.test("mx1.example.com", "mx1.example.org"));
assert.strictEqual(false, op.test("mx1.example.com", "mx1.example.com"));
assert.strictEqual(true, op.test(1, "1")); // variable is not typecasted (strict equality)
});
test("StringNotEqualsOperator returns true for different strings and false for identical strings", () => {
const op = operatorMap.get(OP_STR_NOT_EQUALS);
assert.strictEqual(true, op.test("mx1.example.com", "mx1.example.org"));
assert.strictEqual(false, op.test("mx1.example.com", "mx1.example.com"));
assert.strictEqual(true, op.test(1, "1")); // variable is not typecasted (strict equality)
});
test("Test ContainsOperator with scalar", async (t) => {
const op = operatorMap.get(OP_CONTAINS);
assert.strictEqual(true, op.test("mx1.example.org", "example.org"));
assert.strictEqual(false, op.test("mx1.example.org", "example.com"));
});
test("ContainsOperator returns true when scalar contains substring", () => {
const op = operatorMap.get(OP_CONTAINS);
assert.strictEqual(true, op.test("mx1.example.org", "example.org"));
assert.strictEqual(false, op.test("mx1.example.org", "example.com"));
});
test("Test ContainsOperator with array", async (t) => {
const op = operatorMap.get(OP_CONTAINS);
assert.strictEqual(true, op.test([ "example.org" ], "example.org"));
assert.strictEqual(false, op.test([ "example.org" ], "example.com"));
});
test("ContainsOperator returns true when array contains element", () => {
const op = operatorMap.get(OP_CONTAINS);
assert.strictEqual(true, op.test([ "example.org" ], "example.org"));
assert.strictEqual(false, op.test([ "example.org" ], "example.com"));
});
test("Test NotContainsOperator with scalar", async (t) => {
const op = operatorMap.get(OP_NOT_CONTAINS);
assert.strictEqual(true, op.test("example.org", ".com"));
assert.strictEqual(false, op.test("example.org", ".org"));
});
test("NotContainsOperator returns true when scalar does not contain substring", () => {
const op = operatorMap.get(OP_NOT_CONTAINS);
assert.strictEqual(true, op.test("example.org", ".com"));
assert.strictEqual(false, op.test("example.org", ".org"));
});
test("Test NotContainsOperator with array", async (t) => {
const op = operatorMap.get(OP_NOT_CONTAINS);
assert.strictEqual(true, op.test([ "example.org" ], "example.com"));
assert.strictEqual(false, op.test([ "example.org" ], "example.org"));
});
test("NotContainsOperator returns true when array does not contain element", () => {
const op = operatorMap.get(OP_NOT_CONTAINS);
assert.strictEqual(true, op.test([ "example.org" ], "example.com"));
assert.strictEqual(false, op.test([ "example.org" ], "example.org"));
});
test("Test StartsWithOperator", async (t) => {
const op = operatorMap.get(OP_STARTS_WITH);
assert.strictEqual(true, op.test("mx1.example.com", "mx1"));
assert.strictEqual(false, op.test("mx1.example.com", "mx2"));
});
test("StartsWithOperator returns true when string starts with prefix", () => {
const op = operatorMap.get(OP_STARTS_WITH);
assert.strictEqual(true, op.test("mx1.example.com", "mx1"));
assert.strictEqual(false, op.test("mx1.example.com", "mx2"));
});
test("Test NotStartsWithOperator", async (t) => {
const op = operatorMap.get(OP_NOT_STARTS_WITH);
assert.strictEqual(true, op.test("mx1.example.com", "mx2"));
assert.strictEqual(false, op.test("mx1.example.com", "mx1"));
});
test("NotStartsWithOperator returns true when string does not start with prefix", () => {
const op = operatorMap.get(OP_NOT_STARTS_WITH);
assert.strictEqual(true, op.test("mx1.example.com", "mx2"));
assert.strictEqual(false, op.test("mx1.example.com", "mx1"));
});
test("Test EndsWithOperator", async (t) => {
const op = operatorMap.get(OP_ENDS_WITH);
assert.strictEqual(true, op.test("mx1.example.com", "example.com"));
assert.strictEqual(false, op.test("mx1.example.com", "example.net"));
});
test("EndsWithOperator returns true when string ends with suffix", () => {
const op = operatorMap.get(OP_ENDS_WITH);
assert.strictEqual(true, op.test("mx1.example.com", "example.com"));
assert.strictEqual(false, op.test("mx1.example.com", "example.net"));
});
test("Test NotEndsWithOperator", async (t) => {
const op = operatorMap.get(OP_NOT_ENDS_WITH);
assert.strictEqual(true, op.test("mx1.example.com", "example.net"));
assert.strictEqual(false, op.test("mx1.example.com", "example.com"));
});
test("NotEndsWithOperator returns true when string does not end with suffix", () => {
const op = operatorMap.get(OP_NOT_ENDS_WITH);
assert.strictEqual(true, op.test("mx1.example.com", "example.net"));
assert.strictEqual(false, op.test("mx1.example.com", "example.com"));
});
test("Test NumberEqualsOperator", async (t) => {
const op = operatorMap.get(OP_NUM_EQUALS);
assert.strictEqual(true, op.test(1, 1));
assert.strictEqual(true, op.test(1, "1"));
assert.strictEqual(false, op.test(1, "2"));
});
test("NumberEqualsOperator returns true for equal numbers with type coercion", () => {
const op = operatorMap.get(OP_NUM_EQUALS);
assert.strictEqual(true, op.test(1, 1));
assert.strictEqual(true, op.test(1, "1"));
assert.strictEqual(false, op.test(1, "2"));
});
test("Test NumberNotEqualsOperator", async (t) => {
const op = operatorMap.get(OP_NUM_NOT_EQUALS);
assert.strictEqual(true, op.test(1, "2"));
assert.strictEqual(false, op.test(1, "1"));
});
test("NumberNotEqualsOperator returns true for different numbers", () => {
const op = operatorMap.get(OP_NUM_NOT_EQUALS);
assert.strictEqual(true, op.test(1, "2"));
assert.strictEqual(false, op.test(1, "1"));
});
test("Test LessThanOperator", async (t) => {
const op = operatorMap.get(OP_LT);
assert.strictEqual(true, op.test(1, 2));
assert.strictEqual(true, op.test(1, "2"));
assert.strictEqual(false, op.test(1, 1));
});
test("LessThanOperator returns true when first number is less than second", () => {
const op = operatorMap.get(OP_LT);
assert.strictEqual(true, op.test(1, 2));
assert.strictEqual(true, op.test(1, "2"));
assert.strictEqual(false, op.test(1, 1));
});
test("Test GreaterThanOperator", async (t) => {
const op = operatorMap.get(OP_GT);
assert.strictEqual(true, op.test(2, 1));
assert.strictEqual(true, op.test(2, "1"));
assert.strictEqual(false, op.test(1, 1));
});
test("GreaterThanOperator returns true when first number is greater than second", () => {
const op = operatorMap.get(OP_GT);
assert.strictEqual(true, op.test(2, 1));
assert.strictEqual(true, op.test(2, "1"));
assert.strictEqual(false, op.test(1, 1));
});
test("Test LessThanOrEqualToOperator", async (t) => {
const op = operatorMap.get(OP_LTE);
assert.strictEqual(true, op.test(1, 1));
assert.strictEqual(true, op.test(1, 2));
assert.strictEqual(true, op.test(1, "2"));
assert.strictEqual(false, op.test(1, 0));
});
test("LessThanOrEqualToOperator returns true when first number is less than or equal to second", () => {
const op = operatorMap.get(OP_LTE);
assert.strictEqual(true, op.test(1, 1));
assert.strictEqual(true, op.test(1, 2));
assert.strictEqual(true, op.test(1, "2"));
assert.strictEqual(false, op.test(1, 0));
});
test("Test GreaterThanOrEqualToOperator", async (t) => {
const op = operatorMap.get(OP_GTE);
assert.strictEqual(true, op.test(1, 1));
assert.strictEqual(true, op.test(2, 1));
assert.strictEqual(true, op.test(2, "2"));
assert.strictEqual(false, op.test(2, 3));
test("GreaterThanOrEqualToOperator returns true when first number is greater than or equal to second", () => {
const op = operatorMap.get(OP_GTE);
assert.strictEqual(true, op.test(1, 1));
assert.strictEqual(true, op.test(2, 1));
assert.strictEqual(true, op.test(2, "2"));
assert.strictEqual(false, op.test(2, 3));
});
});

View File

@ -2,8 +2,8 @@ const { describe, test } = require("node:test");
const assert = require("node:assert");
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");
const { GrpcKeywordMonitorType } = require("../../server/monitor-types/grpc");
const { UP, PENDING } = require("../../src/util");
const { GrpcKeywordMonitorType } = require("../../../server/monitor-types/grpc");
const { UP, PENDING } = require("../../../src/util");
const fs = require("fs");
const path = require("path");
const os = require("os");
@ -82,7 +82,7 @@ async function createTestGrpcServer(port, methodHandlers) {
describe("GrpcKeywordMonitorType", {
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64"),
}, () => {
test("gRPC keyword found in response", async () => {
test("check() sets status to UP when keyword is found in response", async () => {
const port = 50051;
const server = await createTestGrpcServer(port, {
Echo: (call, callback) => {
@ -118,7 +118,7 @@ describe("GrpcKeywordMonitorType", {
}
});
test("gRPC keyword not found in response", async () => {
test("check() rejects when keyword is not found in response", async () => {
const port = 50052;
const server = await createTestGrpcServer(port, {
Echo: (call, callback) => {
@ -158,7 +158,7 @@ describe("GrpcKeywordMonitorType", {
}
});
test("gRPC inverted keyword - keyword present (should fail)", async () => {
test("check() rejects when inverted keyword is present in response", async () => {
const port = 50053;
const server = await createTestGrpcServer(port, {
Echo: (call, callback) => {
@ -198,7 +198,7 @@ describe("GrpcKeywordMonitorType", {
}
});
test("gRPC inverted keyword - keyword not present (should pass)", async () => {
test("check() sets status to UP when inverted keyword is not present in response", async () => {
const port = 50054;
const server = await createTestGrpcServer(port, {
Echo: (call, callback) => {
@ -234,7 +234,7 @@ describe("GrpcKeywordMonitorType", {
}
});
test("gRPC connection failure", async () => {
test("check() rejects when gRPC server is unreachable", async () => {
const grpcMonitor = new GrpcKeywordMonitorType();
const monitor = {
grpcUrl: "localhost:50099",
@ -262,7 +262,7 @@ describe("GrpcKeywordMonitorType", {
);
});
test("gRPC response truncation for long messages", async () => {
test("check() truncates long response messages in error output", async () => {
const port = 50055;
const longMessage = "A".repeat(100) + " with SUCCESS keyword";

View File

@ -2,8 +2,8 @@ const { describe, test } = require("node:test");
const assert = require("node:assert");
const { HiveMQContainer } = require("@testcontainers/hivemq");
const mqtt = require("mqtt");
const { MqttMonitorType } = require("../../server/monitor-types/mqtt");
const { UP, PENDING } = require("../../src/util");
const { MqttMonitorType } = require("../../../server/monitor-types/mqtt");
const { UP, PENDING } = require("../../../src/util");
/**
* Runs an MQTT test with the
@ -58,91 +58,91 @@ describe("MqttMonitorType", {
concurrency: 4,
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64")
}, () => {
test("valid keywords (type=default)", async () => {
test("check() sets status to UP when keyword is found in message (type=default)", async () => {
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
});
test("valid nested topic", async () => {
test("check() sets status to UP when keyword is found in nested topic", async () => {
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/b/c", "a/b/c");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Topic: a/b/c; Message: -> KEYWORD <-");
});
test("valid nested topic (with special chars)", async () => {
test("check() sets status to UP when keyword is found in nested topic with special characters", async () => {
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/'/$/./*/%", "a/'/$/./*/%");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Topic: a/'/$/./*/%; Message: -> KEYWORD <-");
});
test("valid wildcard topic (with #)", async () => {
test("check() sets status to UP when keyword is found using # wildcard", async () => {
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/#", "a/b/c");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Topic: a/b/c; Message: -> KEYWORD <-");
});
test("valid wildcard topic (with +)", async () => {
test("check() sets status to UP when keyword is found using + wildcard", async () => {
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/+/c", "a/b/c");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Topic: a/b/c; Message: -> KEYWORD <-");
});
test("valid wildcard topic (with + and #)", async () => {
test("check() sets status to UP when keyword is found using + and # wildcards", async () => {
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/+/c/#", "a/b/c/d/e");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Topic: a/b/c/d/e; Message: -> KEYWORD <-");
});
test("invalid topic", async () => {
test("check() rejects with timeout when topic does not match", async () => {
await assert.rejects(
testMqtt("keyword will not be checked anyway", null, "message", "x/y/z", "a/b/c"),
new Error("Timeout, Message not received"),
);
});
test("invalid wildcard topic (with #)", async () => {
test("check() rejects with timeout when # wildcard is not last character", async () => {
await assert.rejects(
testMqtt("", null, "# should be last character", "#/c", "a/b/c"),
new Error("Timeout, Message not received"),
);
});
test("invalid wildcard topic (with +)", async () => {
test("check() rejects with timeout when + wildcard topic does not match", async () => {
await assert.rejects(
testMqtt("", null, "message", "x/+/z", "a/b/c"),
new Error("Timeout, Message not received"),
);
});
test("valid keywords (type=keyword)", async () => {
test("check() sets status to UP when keyword is found in message (type=keyword)", async () => {
const heartbeat = await testMqtt("KEYWORD", "keyword", "-> KEYWORD <-");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
});
test("invalid keywords (type=default)", async () => {
test("check() rejects when keyword is not found in message (type=default)", async () => {
await assert.rejects(
testMqtt("NOT_PRESENT", null, "-> KEYWORD <-"),
new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"),
);
});
test("invalid keyword (type=keyword)", async () => {
test("check() rejects when keyword is not found in message (type=keyword)", async () => {
await assert.rejects(
testMqtt("NOT_PRESENT", "keyword", "-> KEYWORD <-"),
new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"),
);
});
test("valid json-query", async () => {
test("check() sets status to UP when json-query finds expected value", async () => {
// works because the monitors' jsonPath is hard-coded to "firstProp"
const heartbeat = await testMqtt("present", "json-query", "{\"firstProp\":\"present\"}");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Message received, expected value is found");
});
test("invalid (because query fails) json-query", async () => {
test("check() rejects when json-query path returns undefined", async () => {
// works because the monitors' jsonPath is hard-coded to "firstProp"
await assert.rejects(
testMqtt("[not_relevant]", "json-query", "{}"),
@ -150,7 +150,7 @@ describe("MqttMonitorType", {
);
});
test("invalid (because successMessage fails) json-query", async () => {
test("check() rejects when json-query value does not match expected value", async () => {
// works because the monitors' jsonPath is hard-coded to "firstProp"
await assert.rejects(
testMqtt("[wrong_success_messsage]", "json-query", "{\"firstProp\":\"present\"}"),

View File

@ -1,8 +1,8 @@
const { describe, test } = require("node:test");
const assert = require("node:assert");
const { MSSQLServerContainer } = require("@testcontainers/mssqlserver");
const { MssqlMonitorType } = require("../../server/monitor-types/mssql");
const { UP, PENDING } = require("../../src/util");
const { MssqlMonitorType } = require("../../../server/monitor-types/mssql");
const { UP, PENDING } = require("../../../src/util");
/**
* Helper function to create and start a MSSQL container
@ -26,7 +26,7 @@ describe(
(process.platform !== "linux" || process.arch !== "x64"),
},
() => {
test("MSSQL is running", async () => {
test("check() sets status to UP when MSSQL server is reachable", async () => {
let mssqlContainer;
try {
@ -69,7 +69,7 @@ describe(
}
});
test("MSSQL with custom query returning single value", async () => {
test("check() sets status to UP when custom query returns single value", async () => {
const mssqlContainer = await createAndStartMSSQLContainer();
const mssqlMonitor = new MssqlMonitorType();
@ -97,7 +97,7 @@ describe(
}
});
test("MSSQL with custom query and condition that passes", async () => {
test("check() sets status to UP when custom query result meets condition", async () => {
const mssqlContainer = await createAndStartMSSQLContainer();
const mssqlMonitor = new MssqlMonitorType();
@ -133,7 +133,7 @@ describe(
}
});
test("MSSQL with custom query and condition that fails", async () => {
test("check() rejects when custom query result does not meet condition", async () => {
const mssqlContainer = await createAndStartMSSQLContainer();
const mssqlMonitor = new MssqlMonitorType();
@ -174,7 +174,7 @@ describe(
}
});
test("MSSQL query returns no results", async () => {
test("check() rejects when query returns no results", async () => {
const mssqlContainer = await createAndStartMSSQLContainer();
const mssqlMonitor = new MssqlMonitorType();
@ -207,7 +207,7 @@ describe(
}
});
test("MSSQL query returns multiple rows", async () => {
test("check() rejects when query returns multiple rows", async () => {
const mssqlContainer = await createAndStartMSSQLContainer();
const mssqlMonitor = new MssqlMonitorType();
@ -240,7 +240,7 @@ describe(
}
});
test("MSSQL query returns multiple columns", async () => {
test("check() rejects when query returns multiple columns", async () => {
const mssqlContainer = await createAndStartMSSQLContainer();
const mssqlMonitor = new MssqlMonitorType();
@ -273,7 +273,7 @@ describe(
}
});
test("MSSQL is not running", async () => {
test("check() rejects when MSSQL server is not reachable", async () => {
const mssqlMonitor = new MssqlMonitorType();
const monitor = {
databaseConnectionString:

View File

@ -1,8 +1,8 @@
const { describe, test } = require("node:test");
const assert = require("node:assert");
const { PostgreSqlContainer } = require("@testcontainers/postgresql");
const { PostgresMonitorType } = require("../../server/monitor-types/postgres");
const { UP, PENDING } = require("../../src/util");
const { PostgresMonitorType } = require("../../../server/monitor-types/postgres");
const { UP, PENDING } = require("../../../src/util");
describe(
"Postgres Single Node",
@ -12,7 +12,7 @@ describe(
(process.platform !== "linux" || process.arch !== "x64"),
},
() => {
test("Postgres is running", async () => {
test("check() sets status to UP when Postgres server is reachable", async () => {
// The default timeout of 30 seconds might not be enough for the container to start
const postgresContainer = await new PostgreSqlContainer(
"postgres:latest"
@ -37,7 +37,7 @@ describe(
}
});
test("Postgres is not running", async () => {
test("check() rejects when Postgres server is not reachable", async () => {
const postgresMonitor = new PostgresMonitorType();
const monitor = {
databaseConnectionString: "http://localhost:15432",

View File

@ -1,13 +1,13 @@
const { describe, test } = require("node:test");
const assert = require("node:assert");
const { RabbitMQContainer } = require("@testcontainers/rabbitmq");
const { RabbitMqMonitorType } = require("../../server/monitor-types/rabbitmq");
const { UP, PENDING } = require("../../src/util");
const { RabbitMqMonitorType } = require("../../../server/monitor-types/rabbitmq");
const { UP, PENDING } = require("../../../src/util");
describe("RabbitMQ Single Node", {
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64"),
}, () => {
test("RabbitMQ is running", async () => {
test("check() sets status to UP when RabbitMQ server is reachable", async () => {
// The default timeout of 30 seconds might not be enough for the container to start
const rabbitMQContainer = await new RabbitMQContainer().withStartupTimeout(60000).start();
const rabbitMQMonitor = new RabbitMqMonitorType();
@ -33,7 +33,7 @@ describe("RabbitMQ Single Node", {
}
});
test("RabbitMQ is not running", async () => {
test("check() rejects when RabbitMQ server is not reachable", async () => {
const rabbitMQMonitor = new RabbitMqMonitorType();
const monitor = {
rabbitmqNodes: JSON.stringify([ "http://localhost:15672" ]),

View File

@ -1,14 +1,9 @@
const { describe, test } = require("node:test");
const assert = require("node:assert");
const { TCPMonitorType } = require("../../server/monitor-types/tcp");
const { UP, PENDING } = require("../../src/util");
const { TCPMonitorType } = require("../../../server/monitor-types/tcp");
const { UP, PENDING } = require("../../../src/util");
const net = require("net");
/**
* Test suite for TCP Monitor functionality
* This test suite checks the behavior of the TCPMonitorType class
* under different network connection scenarios.
*/
describe("TCP Monitor", () => {
/**
* Creates a TCP server on a specified port
@ -29,11 +24,7 @@ describe("TCP Monitor", () => {
});
}
/**
* Test case to verify TCP monitor works when a server is running
* Checks that the monitor correctly identifies an active TCP server
*/
test("TCP server is running", async () => {
test("check() sets status to UP when TCP server is reachable", async () => {
const port = 12345;
const server = await createTCPServer(port);
@ -59,11 +50,7 @@ describe("TCP Monitor", () => {
}
});
/**
* Test case to verify TCP monitor handles non-running servers
* Checks that the monitor correctly identifies an inactive TCP server
*/
test("TCP server is not running", async () => {
test("check() rejects with connection failed when TCP server is not running", async () => {
const tcpMonitor = new TCPMonitorType();
const monitor = {
@ -83,11 +70,7 @@ describe("TCP Monitor", () => {
);
});
/**
* Test case to verify TCP monitor handles servers with expired or invalid TLS certificates
* Checks that the monitor correctly identifies TLS certificate issues
*/
test("TCP server with expired or invalid TLS certificate", async t => {
test("check() rejects when TLS certificate is expired or invalid", async () => {
const tcpMonitor = new TCPMonitorType();
const monitor = {
@ -114,7 +97,7 @@ describe("TCP Monitor", () => {
);
});
test("TCP server with valid TLS certificate (SSL)", async t => {
test("check() sets status to UP when TLS certificate is valid (SSL)", async () => {
const tcpMonitor = new TCPMonitorType();
const monitor = {
@ -137,7 +120,7 @@ describe("TCP Monitor", () => {
assert.strictEqual(heartbeat.status, UP);
});
test("TCP server with valid TLS certificate (STARTTLS)", async t => {
test("check() sets status to UP when TLS certificate is valid (STARTTLS)", async () => {
const tcpMonitor = new TCPMonitorType();
const monitor = {
@ -160,7 +143,7 @@ describe("TCP Monitor", () => {
assert.strictEqual(heartbeat.status, UP);
});
test("TCP server with valid but name mismatching TLS certificate (STARTTLS)", async t => {
test("check() rejects when TLS certificate hostname does not match (STARTTLS)", async () => {
const tcpMonitor = new TCPMonitorType();
const monitor = {
@ -185,7 +168,7 @@ describe("TCP Monitor", () => {
regex
);
});
test("XMPP server with valid certificate (STARTTLS)", async t => {
test("check() sets status to UP for XMPP server with valid certificate (STARTTLS)", async () => {
const tcpMonitor = new TCPMonitorType();
const monitor = {

View File

@ -1,8 +1,8 @@
const { WebSocketServer } = require("ws");
const { describe, test } = require("node:test");
const assert = require("node:assert");
const { WebSocketMonitorType } = require("../../server/monitor-types/websocket-upgrade");
const { UP, PENDING } = require("../../src/util");
const { WebSocketMonitorType } = require("../../../server/monitor-types/websocket-upgrade");
const { UP, PENDING } = require("../../../src/util");
const net = require("node:net");
/**
@ -22,9 +22,9 @@ function nonCompliantWS(port = 8080) {
return new Promise((resolve) => srv.listen(port, () => resolve(srv)));
}
describe("Websocket Test", {
describe("WebSocket Monitor", {
}, () => {
test("Non WS Server", {}, async () => {
test("check() rejects with unexpected server response when connecting to non-WebSocket server", {}, async () => {
const websocketMonitor = new WebSocketMonitorType();
const monitor = {
@ -44,7 +44,7 @@ describe("Websocket Test", {
);
});
test("Secure WS", async () => {
test("check() sets status to UP when connecting to secure WebSocket server", async () => {
const websocketMonitor = new WebSocketMonitorType();
const monitor = {
@ -68,7 +68,7 @@ describe("Websocket Test", {
assert.deepStrictEqual(heartbeat, expected);
});
test("Insecure WS", async (t) => {
test("check() sets status to UP when connecting to insecure WebSocket server", async (t) => {
t.after(() => wss.close());
const websocketMonitor = new WebSocketMonitorType();
const wss = new WebSocketServer({ port: 8080 });
@ -94,7 +94,7 @@ describe("Websocket Test", {
assert.deepStrictEqual(heartbeat, expected);
});
test("Non compliant WS Server wrong status code", async () => {
test("check() rejects when status code does not match expected value", async () => {
const websocketMonitor = new WebSocketMonitorType();
const monitor = {
@ -115,7 +115,7 @@ describe("Websocket Test", {
);
});
test("Secure WS Server no status code", async () => {
test("check() rejects when expected status code is empty", async () => {
const websocketMonitor = new WebSocketMonitorType();
const monitor = {
@ -136,7 +136,7 @@ describe("Websocket Test", {
);
});
test("Non compliant WS server without IgnoreSecWebsocket", async (t) => {
test("check() rejects when Sec-WebSocket-Accept header is invalid", async (t) => {
t.after(() => wss.close());
const websocketMonitor = new WebSocketMonitorType();
const wss = await nonCompliantWS();
@ -159,7 +159,7 @@ describe("Websocket Test", {
);
});
test("Non compliant WS server with IgnoreSecWebsocket", async (t) => {
test("check() sets status to UP when ignoring invalid Sec-WebSocket-Accept header", async (t) => {
t.after(() => wss.close());
const websocketMonitor = new WebSocketMonitorType();
const wss = await nonCompliantWS();
@ -185,7 +185,7 @@ describe("Websocket Test", {
assert.deepStrictEqual(heartbeat, expected);
});
test("Compliant WS server with IgnoreSecWebsocket", async () => {
test("check() sets status to UP for compliant WebSocket server when ignoring Sec-WebSocket-Accept", async () => {
const websocketMonitor = new WebSocketMonitorType();
const monitor = {
@ -209,7 +209,7 @@ describe("Websocket Test", {
assert.deepStrictEqual(heartbeat, expected);
});
test("Non WS server with IgnoreSecWebsocket", async () => {
test("check() rejects non-WebSocket server even when ignoring Sec-WebSocket-Accept", async () => {
const websocketMonitor = new WebSocketMonitorType();
const monitor = {
@ -230,7 +230,7 @@ describe("Websocket Test", {
);
});
test("Secure WS no subprotocol support", async () => {
test("check() rejects when server does not support requested subprotocol", async () => {
const websocketMonitor = new WebSocketMonitorType();
const monitor = {
@ -252,7 +252,7 @@ describe("Websocket Test", {
);
});
test("Multiple subprotocols invalid input", async () => {
test("check() rejects when multiple subprotocols contain invalid characters", async () => {
const websocketMonitor = new WebSocketMonitorType();
const monitor = {
@ -274,7 +274,7 @@ describe("Websocket Test", {
);
});
test("Insecure WS subprotocol multiple spaces", async (t) => {
test("check() sets status to UP when subprotocol with multiple spaces is accepted", async (t) => {
t.after(() => wss.close());
const websocketMonitor = new WebSocketMonitorType();
const wss = new WebSocketServer({ port: 8080,
@ -305,7 +305,7 @@ describe("Websocket Test", {
assert.deepStrictEqual(heartbeat, expected);
});
test("Insecure WS supports one subprotocol", async (t) => {
test("check() sets status to UP when server supports requested subprotocol", async (t) => {
t.after(() => wss.close());
const websocketMonitor = new WebSocketMonitorType();
const wss = new WebSocketServer({ port: 8080,

View File

@ -1,28 +1,28 @@
const express = require("express");
const bodyParser = require("body-parser");
/**
* @param {number} port Port number
* @param {string} url Webhook URL
* @param {number} timeout Timeout
* @returns {Promise<object>} Webhook data
*/
async function mockWebhook(port, url, timeout = 2500) {
return new Promise((resolve, reject) => {
const app = express();
const tmo = setTimeout(() => {
server.close();
reject({ reason: "Timeout" });
}, timeout);
app.use(bodyParser.json()); // Middleware to parse JSON bodies
app.post(`/${url}`, (req, res) => {
res.status(200).send("OK");
server.close();
tmo && clearTimeout(tmo);
resolve(req.body);
});
const server = app.listen(port);
});
}
module.exports = mockWebhook;
const express = require("express");
const bodyParser = require("body-parser");
/**
* @param {number} port Port number
* @param {string} url Webhook URL
* @param {number} timeout Timeout
* @returns {Promise<object>} Webhook data
*/
async function mockWebhook(port, url, timeout = 2500) {
return new Promise((resolve, reject) => {
const app = express();
const tmo = setTimeout(() => {
server.close();
reject({ reason: "Timeout" });
}, timeout);
app.use(bodyParser.json()); // Middleware to parse JSON bodies
app.post(`/${url}`, (req, res) => {
res.status(200).send("OK");
server.close();
tmo && clearTimeout(tmo);
resolve(req.body);
});
const server = app.listen(port);
});
}
module.exports = mockWebhook;

View File

@ -1,4 +1,4 @@
const { test } = require("node:test");
const { describe, test } = require("node:test");
const assert = require("node:assert");
@ -36,12 +36,14 @@ iPenGDCg1awOyRnvxNq1MtMDkR9AHwksukzwiYNexYjyvE2t0UzXhFXwazQ3
-----END CERTIFICATE-----
`;
test("Certificate and hostname match", () => {
const result = checkCertificateHostname(testCert, "www.eff.org");
assert.strictEqual(result, true);
});
describe("Certificate Hostname Validation", () => {
test("checkCertificateHostname() returns true when certificate matches hostname", () => {
const result = checkCertificateHostname(testCert, "www.eff.org");
assert.strictEqual(result, true);
});
test("Certificate and hostname mismatch", () => {
const result = checkCertificateHostname(testCert, "example.com");
assert.strictEqual(result, false);
test("checkCertificateHostname() returns false when certificate does not match hostname", () => {
const result = checkCertificateHostname(testCert, "example.com");
assert.strictEqual(result, false);
});
});

View File

@ -1,9 +1,9 @@
process.env.UPTIME_KUMA_HIDE_LOG = [ "info_db", "info_server" ].join(",");
const test = require("node:test");
const { describe, test } = require("node:test");
const assert = require("node:assert");
const DomainExpiry = require("../../server/model/domain_expiry");
const mockWebhook = require("../mock-webhook");
const mockWebhook = require("./notification-providers/mock-webhook");
const TestDB = require("../mock-testdb");
const { R } = require("redbean-node");
const { Notification } = require("../../server/notification");
@ -12,30 +12,34 @@ const { setSetting } = require("../../server/util-server");
const testDb = new TestDB();
test("Domain Expiry", async (t) => {
await testDb.create();
Notification.init();
describe("Domain Expiry", () => {
const monHttpCom = {
type: "http",
url: "https://www.google.com",
domainExpiryNotification: true
};
await t.test("Should get expiry date for .wiki with no A record", async () => {
test("getExpiryDate() returns correct expiry date for .wiki domain with no A record", async () => {
await testDb.create();
Notification.init();
const d = DomainExpiry.createByName("google.wiki");
assert.deepEqual(await d.getExpiryDate(), new Date("2026-11-26T23:59:59.000Z"));
});
await t.test("Should get expiration date for .com from RDAP", async () => {
test("forMonitor() retrieves expiration date for .com domain from RDAP", async () => {
const domain = await DomainExpiry.forMonitor(monHttpCom);
const expiryFromRdap = await domain.getExpiryDate(); // from RDAP
assert.deepEqual(expiryFromRdap, new Date("2028-09-14T04:00:00.000Z"));
});
await t.test("Should have expiration date cached in database", async () => {
test("checkExpiry() caches expiration date in database", async () => {
await DomainExpiry.checkExpiry(monHttpCom); // RDAP -> Cache
const domain = await DomainExpiry.findByName("google.com");
assert(Date.now() - domain.lastCheck < 5 * 1000);
});
await t.test("Should trigger notify for expiring domain", async () => {
test("sendNotifications() triggers notification for expiring domain", async () => {
await DomainExpiry.findByName("google.com");
const hook = {
"port": 3010,
@ -60,10 +64,10 @@ test("Domain Expiry", async (t) => {
mockWebhook(hook.port, hook.url)
]);
assert.match(data.msg, /will expire in/);
setTimeout(async () => {
Settings.stopCacheCleaner();
await testDb.destroy();
}, 200);
});
}).finally(() => {
setTimeout(async () => {
Settings.stopCacheCleaner();
await testDb.destroy();
}, 200);
});

View File

@ -1,65 +0,0 @@
const test = require("node:test");
const assert = require("node:assert");
const dayjs = require("dayjs");
const { SQL_DATETIME_FORMAT } = require("../../src/util");
dayjs.extend(require("dayjs/plugin/utc"));
dayjs.extend(require("dayjs/plugin/customParseFormat"));
/**
* Tests for maintenance date formatting to ensure compatibility with MariaDB/MySQL.
* Issue: MariaDB rejects ISO format dates like '2025-12-19T01:04:02.129Z'
* Fix: Use SQL_DATETIME_FORMAT ('YYYY-MM-DD HH:mm:ss') instead of toISOString()
*/
test("Maintenance Date Format - MariaDB Compatibility", async (t) => {
await t.test("SQL_DATETIME_FORMAT constant should match MariaDB format", async () => {
assert.strictEqual(SQL_DATETIME_FORMAT, "YYYY-MM-DD HH:mm:ss");
});
await t.test("Format date using SQL_DATETIME_FORMAT", async () => {
const current = dayjs.utc("2025-12-19T01:04:02.129Z");
const sqlFormat = current.utc().format(SQL_DATETIME_FORMAT);
assert.strictEqual(sqlFormat, "2025-12-19 01:04:02");
});
await t.test("SQL format should not contain ISO markers (T, Z)", async () => {
const current = dayjs.utc("2025-12-19T01:04:02.129Z");
const sqlFormat = current.utc().format(SQL_DATETIME_FORMAT);
assert.strictEqual(sqlFormat.includes("T"), false, "SQL format should not contain 'T'");
assert.strictEqual(sqlFormat.includes("Z"), false, "SQL format should not contain 'Z'");
});
await t.test("SQL format should match YYYY-MM-DD HH:mm:ss pattern", async () => {
const current = dayjs.utc("2025-12-19T01:04:02.129Z");
const sqlFormat = current.utc().format(SQL_DATETIME_FORMAT);
const sqlDateTimeRegex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
assert.strictEqual(sqlDateTimeRegex.test(sqlFormat), true);
});
await t.test("Parse SQL datetime back to dayjs preserves timestamp", async () => {
const originalDate = dayjs.utc("2025-12-19T01:04:02.000Z");
const sqlFormat = originalDate.utc().format(SQL_DATETIME_FORMAT);
const parsedDate = dayjs.utc(sqlFormat, SQL_DATETIME_FORMAT);
assert.strictEqual(parsedDate.unix(), originalDate.unix());
});
await t.test("Edge case: midnight timestamp", async () => {
const midnight = dayjs.utc("2025-01-01T00:00:00.000Z");
const sqlFormat = midnight.utc().format(SQL_DATETIME_FORMAT);
assert.strictEqual(sqlFormat, "2025-01-01 00:00:00");
});
await t.test("Edge case: end of day timestamp", async () => {
const endOfDay = dayjs.utc("2025-12-31T23:59:59.999Z");
const sqlFormat = endOfDay.utc().format(SQL_DATETIME_FORMAT);
assert.strictEqual(sqlFormat, "2025-12-31 23:59:59");
});
});

View File

@ -3,8 +3,8 @@ const fs = require("fs");
const path = require("path");
const { GenericContainer, Wait } = require("testcontainers");
describe("Database Migration - Optimize Important Indexes", () => {
test("SQLite: All migrations run successfully", async () => {
describe("Database Migration", () => {
test("SQLite migrations run successfully from fresh database", async () => {
const testDbPath = path.join(__dirname, "../../data/test-migration.db");
const testDbDir = path.dirname(testDbPath);
@ -57,7 +57,7 @@ describe("Database Migration - Optimize Important Indexes", () => {
});
test(
"MariaDB: All migrations run successfully",
"MariaDB migrations run successfully from fresh database",
{
skip:
!!process.env.CI &&

View File

@ -1,4 +1,4 @@
const test = require("node:test");
const { describe, test } = require("node:test");
const assert = require("node:assert");
const { UptimeCalculator } = require("../../server/uptime-calculator");
const dayjs = require("dayjs");
@ -7,344 +7,403 @@ dayjs.extend(require("dayjs/plugin/utc"));
dayjs.extend(require("../../server/modules/dayjs/plugin/timezone"));
dayjs.extend(require("dayjs/plugin/customParseFormat"));
test("Test Uptime Calculator - custom date", async (t) => {
let c1 = new UptimeCalculator();
describe("Uptime Calculator", () => {
test("getCurrentDate() returns custom date when set", () => {
let c1 = new UptimeCalculator();
// Test custom date
UptimeCalculator.currentDate = dayjs.utc("2021-01-01T00:00:00.000Z");
assert.strictEqual(c1.getCurrentDate().unix(), dayjs.utc("2021-01-01T00:00:00.000Z").unix());
});
// Test custom date
UptimeCalculator.currentDate = dayjs.utc("2021-01-01T00:00:00.000Z");
assert.strictEqual(c1.getCurrentDate().unix(), dayjs.utc("2021-01-01T00:00:00.000Z").unix());
});
test("Test update - UP", async (t) => {
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
let c2 = new UptimeCalculator();
let date = await c2.update(UP);
assert.strictEqual(date.unix(), dayjs.utc("2023-08-12 20:46:59").unix());
});
test("update() with UP status returns correct timestamp", async () => {
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
let c2 = new UptimeCalculator();
let date = await c2.update(UP);
assert.strictEqual(date.unix(), dayjs.utc("2023-08-12 20:46:59").unix());
});
test("Test update - MAINTENANCE", async (t) => {
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:47:20");
let c2 = new UptimeCalculator();
let date = await c2.update(MAINTENANCE);
assert.strictEqual(date.unix(), dayjs.utc("2023-08-12 20:47:20").unix());
});
test("update() with MAINTENANCE status returns correct timestamp", async () => {
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:47:20");
let c2 = new UptimeCalculator();
let date = await c2.update(MAINTENANCE);
assert.strictEqual(date.unix(), dayjs.utc("2023-08-12 20:47:20").unix());
});
test("Test update - DOWN", async (t) => {
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:47:20");
let c2 = new UptimeCalculator();
let date = await c2.update(DOWN);
assert.strictEqual(date.unix(), dayjs.utc("2023-08-12 20:47:20").unix());
});
test("update() with DOWN status returns correct timestamp", async () => {
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:47:20");
let c2 = new UptimeCalculator();
let date = await c2.update(DOWN);
assert.strictEqual(date.unix(), dayjs.utc("2023-08-12 20:47:20").unix());
});
test("Test update - PENDING", async (t) => {
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:47:20");
let c2 = new UptimeCalculator();
let date = await c2.update(PENDING);
assert.strictEqual(date.unix(), dayjs.utc("2023-08-12 20:47:20").unix());
});
test("update() with PENDING status returns correct timestamp", async () => {
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:47:20");
let c2 = new UptimeCalculator();
let date = await c2.update(PENDING);
assert.strictEqual(date.unix(), dayjs.utc("2023-08-12 20:47:20").unix());
});
test("Test flatStatus", async (t) => {
let c2 = new UptimeCalculator();
assert.strictEqual(c2.flatStatus(UP), UP);
//assert.strictEqual(c2.flatStatus(MAINTENANCE), UP);
assert.strictEqual(c2.flatStatus(DOWN), DOWN);
assert.strictEqual(c2.flatStatus(PENDING), DOWN);
});
test("flatStatus() converts statuses correctly", () => {
let c2 = new UptimeCalculator();
assert.strictEqual(c2.flatStatus(UP), UP);
//assert.strictEqual(c2.flatStatus(MAINTENANCE), UP);
assert.strictEqual(c2.flatStatus(DOWN), DOWN);
assert.strictEqual(c2.flatStatus(PENDING), DOWN);
});
test("Test getMinutelyKey", async (t) => {
let c2 = new UptimeCalculator();
let divisionKey = c2.getMinutelyKey(dayjs.utc("2023-08-12 20:46:00"));
assert.strictEqual(divisionKey, dayjs.utc("2023-08-12 20:46:00").unix());
test("getMinutelyKey() returns correct timestamp for start of minute", () => {
let c2 = new UptimeCalculator();
let divisionKey = c2.getMinutelyKey(dayjs.utc("2023-08-12 20:46:00"));
assert.strictEqual(divisionKey, dayjs.utc("2023-08-12 20:46:00").unix());
// Edge case 1
c2 = new UptimeCalculator();
divisionKey = c2.getMinutelyKey(dayjs.utc("2023-08-12 20:46:01"));
assert.strictEqual(divisionKey, dayjs.utc("2023-08-12 20:46:00").unix());
// Edge case 1
c2 = new UptimeCalculator();
divisionKey = c2.getMinutelyKey(dayjs.utc("2023-08-12 20:46:01"));
assert.strictEqual(divisionKey, dayjs.utc("2023-08-12 20:46:00").unix());
// Edge case 2
c2 = new UptimeCalculator();
divisionKey = c2.getMinutelyKey(dayjs.utc("2023-08-12 20:46:59"));
assert.strictEqual(divisionKey, dayjs.utc("2023-08-12 20:46:00").unix());
});
// Edge case 2
c2 = new UptimeCalculator();
divisionKey = c2.getMinutelyKey(dayjs.utc("2023-08-12 20:46:59"));
assert.strictEqual(divisionKey, dayjs.utc("2023-08-12 20:46:00").unix());
});
test("Test getDailyKey", async (t) => {
let c2 = new UptimeCalculator();
let dailyKey = c2.getDailyKey(dayjs.utc("2023-08-12 20:46:00"));
assert.strictEqual(dailyKey, dayjs.utc("2023-08-12").unix());
test("getDailyKey() returns correct timestamp for start of day", () => {
let c2 = new UptimeCalculator();
let dailyKey = c2.getDailyKey(dayjs.utc("2023-08-12 20:46:00"));
assert.strictEqual(dailyKey, dayjs.utc("2023-08-12").unix());
c2 = new UptimeCalculator();
dailyKey = c2.getDailyKey(dayjs.utc("2023-08-12 23:45:30"));
assert.strictEqual(dailyKey, dayjs.utc("2023-08-12").unix());
c2 = new UptimeCalculator();
dailyKey = c2.getDailyKey(dayjs.utc("2023-08-12 23:45:30"));
assert.strictEqual(dailyKey, dayjs.utc("2023-08-12").unix());
// Edge case 1
c2 = new UptimeCalculator();
dailyKey = c2.getDailyKey(dayjs.utc("2023-08-12 23:59:59"));
assert.strictEqual(dailyKey, dayjs.utc("2023-08-12").unix());
// Edge case 1
c2 = new UptimeCalculator();
dailyKey = c2.getDailyKey(dayjs.utc("2023-08-12 23:59:59"));
assert.strictEqual(dailyKey, dayjs.utc("2023-08-12").unix());
// Edge case 2
c2 = new UptimeCalculator();
dailyKey = c2.getDailyKey(dayjs.utc("2023-08-12 00:00:00"));
assert.strictEqual(dailyKey, dayjs.utc("2023-08-12").unix());
// Edge case 2
c2 = new UptimeCalculator();
dailyKey = c2.getDailyKey(dayjs.utc("2023-08-12 00:00:00"));
assert.strictEqual(dailyKey, dayjs.utc("2023-08-12").unix());
// Test timezone
c2 = new UptimeCalculator();
dailyKey = c2.getDailyKey(dayjs("Sat Dec 23 2023 05:38:39 GMT+0800 (Hong Kong Standard Time)"));
assert.strictEqual(dailyKey, dayjs.utc("2023-12-22").unix());
});
// Test timezone
c2 = new UptimeCalculator();
dailyKey = c2.getDailyKey(dayjs("Sat Dec 23 2023 05:38:39 GMT+0800 (Hong Kong Standard Time)"));
assert.strictEqual(dailyKey, dayjs.utc("2023-12-22").unix());
});
test("Test lastDailyUptimeData", async (t) => {
let c2 = new UptimeCalculator();
await c2.update(UP);
assert.strictEqual(c2.lastDailyUptimeData.up, 1);
});
test("lastDailyUptimeData tracks UP status correctly", async () => {
let c2 = new UptimeCalculator();
await c2.update(UP);
assert.strictEqual(c2.lastDailyUptimeData.up, 1);
});
test("Test get24Hour Uptime and Avg Ping", async (t) => {
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
test("get24Hour() calculates uptime and average ping correctly", async () => {
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
// No data
let c2 = new UptimeCalculator();
let data = c2.get24Hour();
assert.strictEqual(data.uptime, 0);
assert.strictEqual(data.avgPing, null);
// No data
let c2 = new UptimeCalculator();
let data = c2.get24Hour();
assert.strictEqual(data.uptime, 0);
assert.strictEqual(data.avgPing, null);
// 1 Up
c2 = new UptimeCalculator();
await c2.update(UP, 100);
let uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 1);
assert.strictEqual(c2.get24Hour().avgPing, 100);
// 1 Up
c2 = new UptimeCalculator();
await c2.update(UP, 100);
let uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 1);
assert.strictEqual(c2.get24Hour().avgPing, 100);
// 2 Up
c2 = new UptimeCalculator();
await c2.update(UP, 100);
await c2.update(UP, 200);
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 1);
assert.strictEqual(c2.get24Hour().avgPing, 150);
// 2 Up
c2 = new UptimeCalculator();
await c2.update(UP, 100);
await c2.update(UP, 200);
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 1);
assert.strictEqual(c2.get24Hour().avgPing, 150);
// 3 Up
c2 = new UptimeCalculator();
await c2.update(UP, 0);
await c2.update(UP, 100);
await c2.update(UP, 400);
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 1);
assert.strictEqual(c2.get24Hour().avgPing, 166.66666666666666);
// 3 Up
c2 = new UptimeCalculator();
await c2.update(UP, 0);
await c2.update(UP, 100);
await c2.update(UP, 400);
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 1);
assert.strictEqual(c2.get24Hour().avgPing, 166.66666666666666);
// 1 MAINTENANCE
c2 = new UptimeCalculator();
await c2.update(MAINTENANCE);
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 0);
assert.strictEqual(c2.get24Hour().avgPing, null);
// 1 MAINTENANCE
c2 = new UptimeCalculator();
await c2.update(MAINTENANCE);
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 0);
assert.strictEqual(c2.get24Hour().avgPing, null);
// 1 PENDING
c2 = new UptimeCalculator();
await c2.update(PENDING);
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 0);
assert.strictEqual(c2.get24Hour().avgPing, null);
// 1 PENDING
c2 = new UptimeCalculator();
await c2.update(PENDING);
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 0);
assert.strictEqual(c2.get24Hour().avgPing, null);
// 1 DOWN
c2 = new UptimeCalculator();
await c2.update(DOWN);
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 0);
assert.strictEqual(c2.get24Hour().avgPing, null);
// 1 DOWN
c2 = new UptimeCalculator();
await c2.update(DOWN);
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 0);
assert.strictEqual(c2.get24Hour().avgPing, null);
// 2 DOWN
c2 = new UptimeCalculator();
await c2.update(DOWN);
await c2.update(DOWN);
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 0);
assert.strictEqual(c2.get24Hour().avgPing, null);
// 2 DOWN
c2 = new UptimeCalculator();
await c2.update(DOWN);
await c2.update(DOWN);
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 0);
assert.strictEqual(c2.get24Hour().avgPing, null);
// 1 DOWN, 1 UP
c2 = new UptimeCalculator();
await c2.update(DOWN);
await c2.update(UP, 0.5);
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 0.5);
assert.strictEqual(c2.get24Hour().avgPing, 0.5);
// 1 DOWN, 1 UP
c2 = new UptimeCalculator();
await c2.update(DOWN);
await c2.update(UP, 0.5);
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 0.5);
assert.strictEqual(c2.get24Hour().avgPing, 0.5);
// 1 UP, 1 DOWN
c2 = new UptimeCalculator();
await c2.update(UP, 123);
await c2.update(DOWN);
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 0.5);
assert.strictEqual(c2.get24Hour().avgPing, 123);
// 1 UP, 1 DOWN
c2 = new UptimeCalculator();
await c2.update(UP, 123);
await c2.update(DOWN);
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 0.5);
assert.strictEqual(c2.get24Hour().avgPing, 123);
// Add 24 hours
c2 = new UptimeCalculator();
await c2.update(UP, 0);
await c2.update(UP, 0);
await c2.update(UP, 0);
await c2.update(UP, 1);
await c2.update(DOWN);
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 0.8);
assert.strictEqual(c2.get24Hour().avgPing, 0.25);
// Add 24 hours
c2 = new UptimeCalculator();
await c2.update(UP, 0);
await c2.update(UP, 0);
await c2.update(UP, 0);
await c2.update(UP, 1);
await c2.update(DOWN);
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 0.8);
assert.strictEqual(c2.get24Hour().avgPing, 0.25);
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(24, "hour");
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(24, "hour");
// After 24 hours, even if there is no data, the uptime should be still 80%
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 0.8);
assert.strictEqual(c2.get24Hour().avgPing, 0.25);
// After 24 hours, even if there is no data, the uptime should be still 80%
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 0.8);
assert.strictEqual(c2.get24Hour().avgPing, 0.25);
// Add more 24 hours (48 hours)
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(24, "hour");
// Add more 24 hours (48 hours)
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(24, "hour");
// After 48 hours, even if there is no data, the uptime should be still 80%
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 0.8);
assert.strictEqual(c2.get24Hour().avgPing, 0.25);
});
// After 48 hours, even if there is no data, the uptime should be still 80%
uptime = c2.get24Hour().uptime;
assert.strictEqual(uptime, 0.8);
assert.strictEqual(c2.get24Hour().avgPing, 0.25);
});
test("Test get7DayUptime", async (t) => {
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
test("get7Day() calculates 7-day uptime correctly", async () => {
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
// No data
let c2 = new UptimeCalculator();
let uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 0);
// No data
let c2 = new UptimeCalculator();
let uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 0);
// 1 Up
c2 = new UptimeCalculator();
await c2.update(UP);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 1);
// 1 Up
c2 = new UptimeCalculator();
await c2.update(UP);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 1);
// 2 Up
c2 = new UptimeCalculator();
await c2.update(UP);
await c2.update(UP);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 1);
// 2 Up
c2 = new UptimeCalculator();
await c2.update(UP);
await c2.update(UP);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 1);
// 3 Up
c2 = new UptimeCalculator();
await c2.update(UP);
await c2.update(UP);
await c2.update(UP);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 1);
// 3 Up
c2 = new UptimeCalculator();
await c2.update(UP);
await c2.update(UP);
await c2.update(UP);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 1);
// 1 MAINTENANCE
c2 = new UptimeCalculator();
await c2.update(MAINTENANCE);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 0);
// 1 MAINTENANCE
c2 = new UptimeCalculator();
await c2.update(MAINTENANCE);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 0);
// 1 PENDING
c2 = new UptimeCalculator();
await c2.update(PENDING);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 0);
// 1 PENDING
c2 = new UptimeCalculator();
await c2.update(PENDING);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 0);
// 1 DOWN
c2 = new UptimeCalculator();
await c2.update(DOWN);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 0);
// 1 DOWN
c2 = new UptimeCalculator();
await c2.update(DOWN);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 0);
// 2 DOWN
c2 = new UptimeCalculator();
await c2.update(DOWN);
await c2.update(DOWN);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 0);
// 2 DOWN
c2 = new UptimeCalculator();
await c2.update(DOWN);
await c2.update(DOWN);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 0);
// 1 DOWN, 1 UP
c2 = new UptimeCalculator();
await c2.update(DOWN);
await c2.update(UP);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 0.5);
// 1 DOWN, 1 UP
c2 = new UptimeCalculator();
await c2.update(DOWN);
await c2.update(UP);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 0.5);
// 1 UP, 1 DOWN
c2 = new UptimeCalculator();
await c2.update(UP);
await c2.update(DOWN);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 0.5);
// 1 UP, 1 DOWN
c2 = new UptimeCalculator();
await c2.update(UP);
await c2.update(DOWN);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 0.5);
// Add 7 days
c2 = new UptimeCalculator();
await c2.update(UP);
await c2.update(UP);
await c2.update(UP);
await c2.update(UP);
await c2.update(DOWN);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 0.8);
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(7, "day");
// Add 7 days
c2 = new UptimeCalculator();
await c2.update(UP);
await c2.update(UP);
await c2.update(UP);
await c2.update(UP);
await c2.update(DOWN);
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 0.8);
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(7, "day");
// After 7 days, even if there is no data, the uptime should be still 80%
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 0.8);
// After 7 days, even if there is no data, the uptime should be still 80%
uptime = c2.get7Day().uptime;
assert.strictEqual(uptime, 0.8);
});
});
test("get30Day() calculates 30-day uptime correctly with 1 check per day", async () => {
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
test("Test get30DayUptime (1 check per day)", async (t) => {
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
let c2 = new UptimeCalculator();
let uptime = c2.get30Day().uptime;
assert.strictEqual(uptime, 0);
let c2 = new UptimeCalculator();
let uptime = c2.get30Day().uptime;
assert.strictEqual(uptime, 0);
let up = 0;
let down = 0;
let flip = true;
for (let i = 0; i < 30; i++) {
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(1, "day");
let up = 0;
let down = 0;
let flip = true;
for (let i = 0; i < 30; i++) {
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(1, "day");
if (flip) {
await c2.update(UP);
up++;
} else {
await c2.update(DOWN);
down++;
}
if (flip) {
await c2.update(UP);
up++;
} else {
await c2.update(DOWN);
down++;
uptime = c2.get30Day().uptime;
assert.strictEqual(uptime, up / (up + down));
flip = !flip;
}
uptime = c2.get30Day().uptime;
assert.strictEqual(uptime, up / (up + down));
// Last 7 days
// Down, Up, Down, Up, Down, Up, Down
// So 3 UP
assert.strictEqual(c2.get7Day().uptime, 3 / 7);
});
flip = !flip;
}
test("get1Year() calculates 1-year uptime correctly with 1 check per day", async () => {
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
// Last 7 days
// Down, Up, Down, Up, Down, Up, Down
// So 3 UP
assert.strictEqual(c2.get7Day().uptime, 3 / 7);
});
let c2 = new UptimeCalculator();
let uptime = c2.get1Year().uptime;
assert.strictEqual(uptime, 0);
test("Test get1YearUptime (1 check per day)", async (t) => {
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
let flip = true;
for (let i = 0; i < 365; i++) {
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(1, "day");
let c2 = new UptimeCalculator();
let uptime = c2.get1Year().uptime;
assert.strictEqual(uptime, 0);
if (flip) {
await c2.update(UP);
} else {
await c2.update(DOWN);
}
let flip = true;
for (let i = 0; i < 365; i++) {
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(1, "day");
if (flip) {
await c2.update(UP);
} else {
await c2.update(DOWN);
uptime = c2.get30Day().time;
flip = !flip;
}
uptime = c2.get30Day().time;
flip = !flip;
}
assert.strictEqual(c2.get1Year().uptime, 183 / 365);
assert.strictEqual(c2.get30Day().uptime, 15 / 30);
assert.strictEqual(c2.get7Day().uptime, 4 / 7);
});
assert.strictEqual(c2.get1Year().uptime, 183 / 365);
assert.strictEqual(c2.get30Day().uptime, 15 / 30);
assert.strictEqual(c2.get7Day().uptime, 4 / 7);
describe("Worst case scenario", () => {
test("handles year-long simulation with various statuses", {
skip: process.env.GITHUB_ACTIONS // Not stable on GitHub Actions"
}, async (t) => {
console.log("Memory usage before preparation", memoryUsage());
let c = new UptimeCalculator();
let up = 0;
let down = 0;
let interval = 20;
await t.test("Prepare data", async () => {
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
// Since 2023-08-12 will be out of 365 range, it starts from 2023-08-13 actually
let actualStartDate = dayjs.utc("2023-08-13 00:00:00").unix();
// Simulate 1s interval for a year
for (let i = 0; i < 365 * 24 * 60 * 60; i += interval) {
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(interval, "second");
//Randomly UP, DOWN, MAINTENANCE, PENDING
let rand = Math.random();
if (rand < 0.25) {
c.update(UP);
if (UptimeCalculator.currentDate.unix() > actualStartDate) {
up++;
}
} else if (rand < 0.5) {
c.update(DOWN);
if (UptimeCalculator.currentDate.unix() > actualStartDate) {
down++;
}
} else if (rand < 0.75) {
c.update(MAINTENANCE);
if (UptimeCalculator.currentDate.unix() > actualStartDate) {
//up++;
}
} else {
c.update(PENDING);
if (UptimeCalculator.currentDate.unix() > actualStartDate) {
down++;
}
}
}
console.log("Final Date: ", UptimeCalculator.currentDate.format("YYYY-MM-DD HH:mm:ss"));
console.log("Memory usage before preparation", memoryUsage());
assert.strictEqual(c.minutelyUptimeDataList.length(), 1440);
assert.strictEqual(c.dailyUptimeDataList.length(), 365);
});
await t.test("get1YearUptime()", async () => {
assert.strictEqual(c.get1Year().uptime, up / (up + down));
});
});
});
});
/**
@ -362,64 +421,3 @@ function memoryUsage() {
external: `${formatMemoryUsage(memoryData.external)} -> V8 external memory`,
};
}
test("Worst case", async (t) => {
// Disable on GitHub Actions, as it is not stable on it
if (process.env.GITHUB_ACTIONS) {
return;
}
console.log("Memory usage before preparation", memoryUsage());
let c = new UptimeCalculator();
let up = 0;
let down = 0;
let interval = 20;
await t.test("Prepare data", async () => {
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
// Since 2023-08-12 will be out of 365 range, it starts from 2023-08-13 actually
let actualStartDate = dayjs.utc("2023-08-13 00:00:00").unix();
// Simulate 1s interval for a year
for (let i = 0; i < 365 * 24 * 60 * 60; i += interval) {
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(interval, "second");
//Randomly UP, DOWN, MAINTENANCE, PENDING
let rand = Math.random();
if (rand < 0.25) {
c.update(UP);
if (UptimeCalculator.currentDate.unix() > actualStartDate) {
up++;
}
} else if (rand < 0.5) {
c.update(DOWN);
if (UptimeCalculator.currentDate.unix() > actualStartDate) {
down++;
}
} else if (rand < 0.75) {
c.update(MAINTENANCE);
if (UptimeCalculator.currentDate.unix() > actualStartDate) {
//up++;
}
} else {
c.update(PENDING);
if (UptimeCalculator.currentDate.unix() > actualStartDate) {
down++;
}
}
}
console.log("Final Date: ", UptimeCalculator.currentDate.format("YYYY-MM-DD HH:mm:ss"));
console.log("Memory usage before preparation", memoryUsage());
assert.strictEqual(c.minutelyUptimeDataList.length(), 1440);
assert.strictEqual(c.dailyUptimeDataList.length(), 365);
});
await t.test("get1YearUptime()", async () => {
assert.strictEqual(c.get1Year().uptime, up / (up + down));
});
});

View File

@ -1,18 +1,46 @@
const test = require("node:test");
const { describe, test } = require("node:test");
const assert = require("node:assert");
const dayjs = require("dayjs");
const { getDaysRemaining, getDaysBetween } = require("../../server/util-server");
const { SQL_DATETIME_FORMAT } = require("../../src/util");
test("Test getDaysBetween", async (t) => {
let days = getDaysBetween(new Date(2025, 9, 7), new Date(2025, 9, 10));
assert.strictEqual(days, 3);
days = getDaysBetween(new Date(2024, 9, 7), new Date(2025, 9, 10));
assert.strictEqual(days, 368);
});
dayjs.extend(require("dayjs/plugin/utc"));
dayjs.extend(require("dayjs/plugin/customParseFormat"));
test("Test getDaysRemaining", async (t) => {
let days = getDaysRemaining(new Date(2025, 9, 7), new Date(2025, 9, 10));
assert.strictEqual(days, 3);
days = getDaysRemaining(new Date(2025, 9, 10), new Date(2025, 9, 7));
assert.strictEqual(days, -3);
describe("Server Utilities", () => {
test("getDaysBetween() calculates days between dates within same month", () => {
const days = getDaysBetween(new Date(2025, 9, 7), new Date(2025, 9, 10));
assert.strictEqual(days, 3);
});
test("getDaysBetween() calculates days between dates across years", () => {
const days = getDaysBetween(new Date(2024, 9, 7), new Date(2025, 9, 10));
assert.strictEqual(days, 368);
});
test("getDaysRemaining() returns positive value when target date is in future", () => {
const days = getDaysRemaining(new Date(2025, 9, 7), new Date(2025, 9, 10));
assert.strictEqual(days, 3);
});
test("getDaysRemaining() returns negative value when target date is in past", () => {
const days = getDaysRemaining(new Date(2025, 9, 10), new Date(2025, 9, 7));
assert.strictEqual(days, -3);
});
test("SQL_DATETIME_FORMAT constant matches MariaDB/MySQL format", () => {
assert.strictEqual(SQL_DATETIME_FORMAT, "YYYY-MM-DD HH:mm:ss");
});
test("SQL_DATETIME_FORMAT produces valid SQL datetime string", () => {
const current = dayjs.utc("2025-12-19T01:04:02.129Z");
const sqlFormat = current.utc().format(SQL_DATETIME_FORMAT);
assert.strictEqual(sqlFormat, "2025-12-19 01:04:02");
// Verify it can be parsed back
const parsedDate = dayjs.utc(sqlFormat, SQL_DATETIME_FORMAT);
assert.strictEqual(parsedDate.unix(), current.unix());
});
});