diff --git a/server/model/domain_expiry.js b/server/model/domain_expiry.js
index b796a81d1..d365c03ab 100644
--- a/server/model/domain_expiry.js
+++ b/server/model/domain_expiry.js
@@ -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);
}
diff --git a/src/lang/en.json b/src/lang/en.json
index 3d01267e7..b6cefcefb 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -1223,5 +1223,6 @@
"labelDomainNameExpiryNotification": "Domain Name Expiry Notification",
"domainExpiryDescription": "Trigger notification when domain names expires in:",
"minimumIntervalWarning": "Intervals below 20 seconds may result in poor performance.",
- "lowIntervalWarning": "Are you sure want to set the interval value below 20 seconds? Performance may be degraded, particularly if there are a large number of monitors."
+ "lowIntervalWarning": "Are you sure want to set the interval value below 20 seconds? Performance may be degraded, particularly if there are a large number of monitors.",
+ "imageResetConfirmation": "Image reset to default"
}
diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue
index 74a5c3f66..7b2426a3c 100644
--- a/src/pages/StatusPage.vue
+++ b/src/pages/StatusPage.vue
@@ -138,6 +138,9 @@
+
@@ -962,6 +965,20 @@ export default {
}
},
+ /**
+ * Reset logo image to default (public/icon.svg)
+ * @returns {void}
+ */
+ resetToDefaultImage() {
+ if (! this.editMode) {
+ return;
+ }
+
+ this.imgDataUrl = "/icon.svg";
+ this.config.icon = this.imgDataUrl;
+ toast.success(this.$t("imageResetConfirmation"));
+ },
+
/**
* Create an incident for this status page
* @returns {void}
@@ -1181,6 +1198,58 @@ footer {
cursor: pointer;
box-shadow: 0 15px 70px rgba(0, 0, 0, 0.9);
}
+
+ /* Reset button placed at top-left of the logo */
+ .reset-top-left {
+ position: absolute;
+ top: 0;
+ left: -15px;
+ z-index: 2;
+ width: 20px;
+ height: 20px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ background: white;
+ border: none;
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
+ cursor: pointer;
+ padding: 0;
+ transition: transform $easing-in 0.18s, box-shadow $easing-in 0.18s, background-color $easing-in 0.18s;
+ transform-origin: center;
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.06);
+ transform: scale(1.18);
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18);
+ }
+
+ &:hover ~ .icon-upload {
+ transform: none !important;
+ }
+ }
+
+ .small-reset-btn {
+ transition: transform $easing-in 0.18s, box-shadow $easing-in 0.18s, background-color $easing-in 0.18s;
+ font-size: 18px;
+ width: 18px;
+ height: 18px;
+ padding: 0;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.04);
+ transform: scale(1.18);
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
+ }
+ }
}
.logo {
diff --git a/test/backend-test/README.md b/test/backend-test/README.md
index 775ffb7a8..8822562ab 100644
--- a/test/backend-test/README.md
+++ b/test/backend-test/README.md
@@ -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);
+ });
});
```
diff --git a/test/backend-test/monitor-conditions/test-evaluator.js b/test/backend-test/monitor-conditions/test-evaluator.js
index da7c7fabf..c731a6792 100644
--- a/test/backend-test/monitor-conditions/test-evaluator.js
+++ b/test/backend-test/monitor-conditions/test-evaluator.js
@@ -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" }));
+ });
});
diff --git a/test/backend-test/monitor-conditions/test-operators.js b/test/backend-test/monitor-conditions/test-operators.js
index e663c9a50..6a6739631 100644
--- a/test/backend-test/monitor-conditions/test-operators.js
+++ b/test/backend-test/monitor-conditions/test-operators.js
@@ -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));
+ });
});
diff --git a/test/backend-test/test-grpc.js b/test/backend-test/monitors/test-grpc.js
similarity index 93%
rename from test/backend-test/test-grpc.js
rename to test/backend-test/monitors/test-grpc.js
index 31b588cff..def839044 100644
--- a/test/backend-test/test-grpc.js
+++ b/test/backend-test/monitors/test-grpc.js
@@ -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";
diff --git a/test/backend-test/test-mqtt.js b/test/backend-test/monitors/test-mqtt.js
similarity index 79%
rename from test/backend-test/test-mqtt.js
rename to test/backend-test/monitors/test-mqtt.js
index 921df48fc..a361e868e 100644
--- a/test/backend-test/test-mqtt.js
+++ b/test/backend-test/monitors/test-mqtt.js
@@ -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\"}"),
diff --git a/test/backend-test/test-mssql.js b/test/backend-test/monitors/test-mssql.js
similarity index 92%
rename from test/backend-test/test-mssql.js
rename to test/backend-test/monitors/test-mssql.js
index afdd1faf8..f265bcdff 100644
--- a/test/backend-test/test-mssql.js
+++ b/test/backend-test/monitors/test-mssql.js
@@ -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:
diff --git a/test/backend-test/test-postgres.js b/test/backend-test/monitors/test-postgres.js
similarity index 84%
rename from test/backend-test/test-postgres.js
rename to test/backend-test/monitors/test-postgres.js
index 71d26d3a1..098c862a1 100644
--- a/test/backend-test/test-postgres.js
+++ b/test/backend-test/monitors/test-postgres.js
@@ -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",
diff --git a/test/backend-test/test-rabbitmq.js b/test/backend-test/monitors/test-rabbitmq.js
similarity index 85%
rename from test/backend-test/test-rabbitmq.js
rename to test/backend-test/monitors/test-rabbitmq.js
index 31f018aa9..63d358dfd 100644
--- a/test/backend-test/test-rabbitmq.js
+++ b/test/backend-test/monitors/test-rabbitmq.js
@@ -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" ]),
diff --git a/test/backend-test/test-tcp.js b/test/backend-test/monitors/test-tcp.js
similarity index 78%
rename from test/backend-test/test-tcp.js
rename to test/backend-test/monitors/test-tcp.js
index 98f168c24..4626fe34f 100644
--- a/test/backend-test/test-tcp.js
+++ b/test/backend-test/monitors/test-tcp.js
@@ -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 = {
diff --git a/test/backend-test/test-websocket.js b/test/backend-test/monitors/test-websocket.js
similarity index 86%
rename from test/backend-test/test-websocket.js
rename to test/backend-test/monitors/test-websocket.js
index 3eeeb3243..d2923f1e7 100644
--- a/test/backend-test/test-websocket.js
+++ b/test/backend-test/monitors/test-websocket.js
@@ -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,
diff --git a/test/mock-webhook.js b/test/backend-test/notification-providers/mock-webhook.js
similarity index 96%
rename from test/mock-webhook.js
rename to test/backend-test/notification-providers/mock-webhook.js
index 23bf192c7..70cc5fdf9 100644
--- a/test/mock-webhook.js
+++ b/test/backend-test/notification-providers/mock-webhook.js
@@ -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