diff options
author | Daniel Baumann <daniel@debian.org> | 2024-11-26 09:28:28 +0100 |
---|---|---|
committer | Daniel Baumann <daniel@debian.org> | 2024-11-26 12:25:58 +0100 |
commit | a1882b67c41fe9901a0cd8059b5cc78a5beadec0 (patch) | |
tree | 2a24507c67aa99a15416707b2f7e645142230ed8 /test | |
parent | Initial commit. (diff) | |
download | uptime-kuma-a1882b67c41fe9901a0cd8059b5cc78a5beadec0.tar.xz uptime-kuma-a1882b67c41fe9901a0cd8059b5cc78a5beadec0.zip |
Adding upstream version 2.0.0~beta.0+dfsg.upstream/2.0.0_beta.0+dfsgupstream
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to 'test')
-rw-r--r-- | test/backend-test/README.md | 22 | ||||
-rw-r--r-- | test/backend-test/monitor-conditions/test-evaluator.js | 46 | ||||
-rw-r--r-- | test/backend-test/monitor-conditions/test-expressions.js | 55 | ||||
-rw-r--r-- | test/backend-test/monitor-conditions/test-operators.js | 108 | ||||
-rw-r--r-- | test/backend-test/test-mqtt.js | 102 | ||||
-rw-r--r-- | test/backend-test/test-rabbitmq.js | 53 | ||||
-rw-r--r-- | test/backend-test/test-uptime-calculator.js | 425 | ||||
-rw-r--r-- | test/e2e/specs/example.spec.js | 39 | ||||
-rw-r--r-- | test/e2e/specs/monitor-form.spec.js | 104 | ||||
-rw-r--r-- | test/e2e/specs/setup-process.once.js | 55 | ||||
-rw-r--r-- | test/e2e/specs/status-page.spec.js | 129 | ||||
-rw-r--r-- | test/e2e/util-test.js | 62 | ||||
-rw-r--r-- | test/prepare-test-server.js | 10 | ||||
-rw-r--r-- | test/test-radius.dockerfile | 13 |
14 files changed, 1223 insertions, 0 deletions
diff --git a/test/backend-test/README.md b/test/backend-test/README.md new file mode 100644 index 0000000..775ffb7 --- /dev/null +++ b/test/backend-test/README.md @@ -0,0 +1,22 @@ +# Node.js Test Runner + +Documentation: https://nodejs.org/api/test.html + +Create a test file in this directory with the name `*.js`. + +## Template + +```js +const test = require("node:test"); +const assert = require("node:assert"); + +test("Test name", async (t) => { + assert.strictEqual(1, 1); +}); +``` + +## Run + +```bash +npm run test-backend +``` diff --git a/test/backend-test/monitor-conditions/test-evaluator.js b/test/backend-test/monitor-conditions/test-evaluator.js new file mode 100644 index 0000000..da7c7fa --- /dev/null +++ b/test/backend-test/monitor-conditions/test-evaluator.js @@ -0,0 +1,46 @@ +const 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" })); +}); + +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("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([ + 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-expressions.js b/test/backend-test/monitor-conditions/test-expressions.js new file mode 100644 index 0000000..fc723a2 --- /dev/null +++ b/test/backend-test/monitor-conditions/test-expressions.js @@ -0,0 +1,55 @@ +const test = require("node:test"); +const assert = require("node:assert"); +const { ConditionExpressionGroup, ConditionExpression } = require("../../../server/monitor-conditions/expression.js"); + +test("Test ConditionExpressionGroup.fromMonitor", async (t) => { + const monitor = { + conditions: JSON.stringify([ + { + "type": "expression", + "andOr": "and", + "operator": "contains", + "value": "foo", + "variable": "record" + }, + { + "type": "group", + "andOr": "and", + "children": [ + { + "type": "expression", + "andOr": "and", + "operator": "contains", + "value": "bar", + "variable": "record" + }, + { + "type": "group", + "andOr": "and", + "children": [ + { + "type": "expression", + "andOr": "and", + "operator": "contains", + "value": "car", + "variable": "record" + } + ] + }, + ] + }, + ]), + }; + const root = ConditionExpressionGroup.fromMonitor(monitor); + assert.strictEqual(true, root.children.length === 2); + assert.strictEqual(true, root.children[0] instanceof ConditionExpression); + assert.strictEqual(true, root.children[0].value === "foo"); + assert.strictEqual(true, root.children[1] instanceof ConditionExpressionGroup); + assert.strictEqual(true, root.children[1].children.length === 2); + assert.strictEqual(true, root.children[1].children[0] instanceof ConditionExpression); + assert.strictEqual(true, root.children[1].children[0].value === "bar"); + assert.strictEqual(true, root.children[1].children[1] instanceof ConditionExpressionGroup); + assert.strictEqual(true, root.children[1].children[1].children.length === 1); + assert.strictEqual(true, root.children[1].children[1].children[0] instanceof ConditionExpression); + assert.strictEqual(true, root.children[1].children[1].children[0].value === "car"); +}); diff --git a/test/backend-test/monitor-conditions/test-operators.js b/test/backend-test/monitor-conditions/test-operators.js new file mode 100644 index 0000000..e663c9a --- /dev/null +++ b/test/backend-test/monitor-conditions/test-operators.js @@ -0,0 +1,108 @@ +const 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 +}); + +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("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("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("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("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("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("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("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("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("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("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("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("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("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("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)); +}); diff --git a/test/backend-test/test-mqtt.js b/test/backend-test/test-mqtt.js new file mode 100644 index 0000000..4503102 --- /dev/null +++ b/test/backend-test/test-mqtt.js @@ -0,0 +1,102 @@ +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"); + +/** + * Runs an MQTT test with the + * @param {string} mqttSuccessMessage the message that the monitor expects + * @param {null|"keyword"|"json-query"} mqttCheckType the type of check we perform + * @param {string} receivedMessage what message is recieved from the mqtt channel + * @returns {Promise<Heartbeat>} the heartbeat produced by the check + */ +async function testMqtt(mqttSuccessMessage, mqttCheckType, receivedMessage) { + const hiveMQContainer = await new HiveMQContainer().start(); + const connectionString = hiveMQContainer.getConnectionString(); + const mqttMonitorType = new MqttMonitorType(); + const monitor = { + jsonPath: "firstProp", // always return firstProp for the json-query monitor + hostname: connectionString.split(":", 2).join(":"), + mqttTopic: "test", + port: connectionString.split(":")[2], + mqttUsername: null, + mqttPassword: null, + interval: 20, // controls the timeout + mqttSuccessMessage: mqttSuccessMessage, // for keywords + expectedValue: mqttSuccessMessage, // for json-query + mqttCheckType: mqttCheckType, + }; + const heartbeat = { + msg: "", + status: PENDING, + }; + + const testMqttClient = mqtt.connect(hiveMQContainer.getConnectionString()); + testMqttClient.on("connect", () => { + testMqttClient.subscribe("test", (error) => { + if (!error) { + testMqttClient.publish("test", receivedMessage); + } + }); + }); + + try { + await mqttMonitorType.check(monitor, heartbeat, {}); + } finally { + testMqttClient.end(); + hiveMQContainer.stop(); + } + return heartbeat; +} + +describe("MqttMonitorType", { + concurrency: true, + skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64") +}, () => { + test("valid keywords (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 keywords (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 () => { + await assert.rejects( + testMqtt("NOT_PRESENT", null, "-> KEYWORD <-"), + new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"), + ); + }); + + test("invalid keyword (type=keyword)", async () => { + await assert.rejects( + testMqtt("NOT_PRESENT", "keyword", "-> KEYWORD <-"), + new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"), + ); + }); + test("valid json-query", 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 () => { + // works because the monitors' jsonPath is hard-coded to "firstProp" + await assert.rejects( + testMqtt("[not_relevant]", "json-query", "{}"), + new Error("Message received but value is not equal to expected value, value was: [undefined]"), + ); + }); + test("invalid (because successMessage fails) json-query", async () => { + // works because the monitors' jsonPath is hard-coded to "firstProp" + await assert.rejects( + testMqtt("[wrong_success_messsage]", "json-query", "{\"firstProp\":\"present\"}"), + new Error("Message received but value is not equal to expected value, value was: [present]") + ); + }); +}); diff --git a/test/backend-test/test-rabbitmq.js b/test/backend-test/test-rabbitmq.js new file mode 100644 index 0000000..5782ef2 --- /dev/null +++ b/test/backend-test/test-rabbitmq.js @@ -0,0 +1,53 @@ +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, DOWN, PENDING } = require("../../src/util"); + +describe("RabbitMQ Single Node", { + skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64"), +}, () => { + test("RabbitMQ is running", 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(); + const connectionString = `http://${rabbitMQContainer.getHost()}:${rabbitMQContainer.getMappedPort(15672)}`; + + const monitor = { + rabbitmqNodes: JSON.stringify([ connectionString ]), + rabbitmqUsername: "guest", + rabbitmqPassword: "guest", + }; + + const heartbeat = { + msg: "", + status: PENDING, + }; + + try { + await rabbitMQMonitor.check(monitor, heartbeat, {}); + assert.strictEqual(heartbeat.status, UP); + assert.strictEqual(heartbeat.msg, "OK"); + } finally { + rabbitMQContainer.stop(); + } + }); + + test("RabbitMQ is not running", async () => { + const rabbitMQMonitor = new RabbitMqMonitorType(); + const monitor = { + rabbitmqNodes: JSON.stringify([ "http://localhost:15672" ]), + rabbitmqUsername: "rabbitmqUser", + rabbitmqPassword: "rabbitmqPass", + }; + + const heartbeat = { + msg: "", + status: PENDING, + }; + + await rabbitMQMonitor.check(monitor, heartbeat, {}); + assert.strictEqual(heartbeat.status, DOWN); + }); + +}); diff --git a/test/backend-test/test-uptime-calculator.js b/test/backend-test/test-uptime-calculator.js new file mode 100644 index 0000000..4f2f05e --- /dev/null +++ b/test/backend-test/test-uptime-calculator.js @@ -0,0 +1,425 @@ +const test = require("node:test"); +const assert = require("node:assert"); +const { UptimeCalculator } = require("../../server/uptime-calculator"); +const dayjs = require("dayjs"); +const { UP, DOWN, PENDING, MAINTENANCE } = require("../../src/util"); +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(); + + // 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("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("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("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("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("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()); + + // 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()); +}); + +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()); + + 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 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("Test lastDailyUptimeData", async (t) => { + 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"); + + // 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); + + // 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); + + // 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 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); + + // 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); + + // 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"); + + // 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"); + + // 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"); + + // 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); + + // 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); + + // 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 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); + + // 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); + + // 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); + +}); + +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 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++; + } + + uptime = c2.get30Day().uptime; + assert.strictEqual(uptime, up / (up + down)); + + flip = !flip; + } + + // Last 7 days + // Down, Up, Down, Up, Down, Up, Down + // So 3 UP + assert.strictEqual(c2.get7Day().uptime, 3 / 7); +}); + +test("Test get1YearUptime (1 check per day)", async (t) => { + UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59"); + + let c2 = new UptimeCalculator(); + let uptime = c2.get1Year().uptime; + assert.strictEqual(uptime, 0); + + 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; + } + + assert.strictEqual(c2.get1Year().uptime, 183 / 365); + assert.strictEqual(c2.get30Day().uptime, 15 / 30); + assert.strictEqual(c2.get7Day().uptime, 4 / 7); +}); + +/** + * Code from here: https://stackoverflow.com/a/64550489/1097815 + * @returns {{rss: string, heapTotal: string, heapUsed: string, external: string}} Current memory usage + */ +function memoryUsage() { + const formatMemoryUsage = (data) => `${Math.round(data / 1024 / 1024 * 100) / 100} MB`; + const memoryData = process.memoryUsage(); + + return { + rss: `${formatMemoryUsage(memoryData.rss)} -> Resident Set Size - total memory allocated for the process execution`, + heapTotal: `${formatMemoryUsage(memoryData.heapTotal)} -> total size of the allocated heap`, + heapUsed: `${formatMemoryUsage(memoryData.heapUsed)} -> actual memory used during the execution`, + 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)); + }); + +}); diff --git a/test/e2e/specs/example.spec.js b/test/e2e/specs/example.spec.js new file mode 100644 index 0000000..4fcfac6 --- /dev/null +++ b/test/e2e/specs/example.spec.js @@ -0,0 +1,39 @@ +import { expect, test } from "@playwright/test"; +import { login, restoreSqliteSnapshot, screenshot } from "../util-test"; + +test.describe("Example Spec", () => { + + test.beforeEach(async ({ page }) => { + await restoreSqliteSnapshot(page); + }); + + test("dashboard", async ({ page }, testInfo) => { + await page.goto("./dashboard"); + await login(page); + await screenshot(testInfo, page); + }); + + test("set up monitor", async ({ page }, testInfo) => { + await page.goto("./add"); + await login(page); + + await expect(page.getByTestId("monitor-type-select")).toBeVisible(); + await page.getByTestId("monitor-type-select").selectOption("http"); + await page.getByTestId("friendly-name-input").fill("example.com"); + await page.getByTestId("url-input").fill("https://www.example.com/"); + await page.getByTestId("save-button").click(); + await page.waitForURL("/dashboard/*"); // wait for the monitor to be created + + await expect(page.getByTestId("monitor-list")).toContainText("example.com"); + await screenshot(testInfo, page); + }); + + test("database is reset after previous test", async ({ page }, testInfo) => { + await page.goto("./dashboard"); + await login(page); + + await expect(page.getByTestId("monitor-list")).not.toContainText("example.com"); + await screenshot(testInfo, page); + }); + +}); diff --git a/test/e2e/specs/monitor-form.spec.js b/test/e2e/specs/monitor-form.spec.js new file mode 100644 index 0000000..7a84f3c --- /dev/null +++ b/test/e2e/specs/monitor-form.spec.js @@ -0,0 +1,104 @@ +import { expect, test } from "@playwright/test"; +import { login, restoreSqliteSnapshot, screenshot } from "../util-test"; + +/** + * Selects the monitor type from the dropdown. + * @param {import('@playwright/test').Page} page - The Playwright page instance. + * @param {string} monitorType - The monitor type to select (default is "dns"). + * @returns {Promise<void>} - A promise that resolves when the monitor type is selected. + */ +async function selectMonitorType(page, monitorType = "dns") { + const monitorTypeSelect = page.getByTestId("monitor-type-select"); + await expect(monitorTypeSelect).toBeVisible(); + await monitorTypeSelect.selectOption(monitorType); + + const selectedValue = await monitorTypeSelect.evaluate((select) => select.value); + expect(selectedValue).toBe(monitorType); +} + +test.describe("Monitor Form", () => { + test.beforeEach(async ({ page }) => { + await restoreSqliteSnapshot(page); + }); + + test("condition ui", async ({ page }, testInfo) => { + await page.goto("./add"); + await login(page); + await screenshot(testInfo, page); + await selectMonitorType(page); + + await page.getByTestId("add-condition-button").click(); + expect(await page.getByTestId("condition").count()).toEqual(2); // 1 added by default + 1 explicitly added + + await page.getByTestId("add-group-button").click(); + expect(await page.getByTestId("condition-group").count()).toEqual(1); + expect(await page.getByTestId("condition").count()).toEqual(3); // 2 solo conditions + 1 condition in group + + await screenshot(testInfo, page); + + await page.getByTestId("remove-condition").first().click(); + expect(await page.getByTestId("condition").count()).toEqual(2); // 1 solo condition + 1 condition in group + + await page.getByTestId("remove-condition-group").first().click(); + expect(await page.getByTestId("condition-group").count()).toEqual(0); + + await screenshot(testInfo, page); + }); + + test("successful condition", async ({ page }, testInfo) => { + await page.goto("./add"); + await login(page); + await screenshot(testInfo, page); + await selectMonitorType(page); + + const friendlyName = "Example DNS NS"; + await page.getByTestId("friendly-name-input").fill(friendlyName); + await page.getByTestId("hostname-input").fill("example.com"); + + const resolveTypeSelect = page.getByTestId("resolve-type-select"); + await resolveTypeSelect.click(); + await resolveTypeSelect.getByRole("option", { name: "NS" }).click(); + + await page.getByTestId("add-condition-button").click(); + expect(await page.getByTestId("condition").count()).toEqual(2); // 1 added by default + 1 explicitly added + + await page.getByTestId("condition-value").nth(0).fill("a.iana-servers.net"); + await page.getByTestId("condition-and-or").nth(0).selectOption("or"); + await page.getByTestId("condition-value").nth(1).fill("b.iana-servers.net"); + + await screenshot(testInfo, page); + await page.getByTestId("save-button").click(); + await page.waitForURL("/dashboard/*"); + + expect(page.getByTestId("monitor-status")).toHaveText("up", { ignoreCase: true }); + + await screenshot(testInfo, page); + }); + + test("failing condition", async ({ page }, testInfo) => { + await page.goto("./add"); + await login(page); + await screenshot(testInfo, page); + await selectMonitorType(page); + + const friendlyName = "Example DNS NS"; + await page.getByTestId("friendly-name-input").fill(friendlyName); + await page.getByTestId("hostname-input").fill("example.com"); + + const resolveTypeSelect = page.getByTestId("resolve-type-select"); + await resolveTypeSelect.click(); + await resolveTypeSelect.getByRole("option", { name: "NS" }).click(); + + expect(await page.getByTestId("condition").count()).toEqual(1); // 1 added by default + + await page.getByTestId("condition-value").nth(0).fill("definitely-not.net"); + + await screenshot(testInfo, page); + await page.getByTestId("save-button").click(); + await page.waitForURL("/dashboard/*"); + + expect(page.getByTestId("monitor-status")).toHaveText("down", { ignoreCase: true }); + + await screenshot(testInfo, page); + }); +}); diff --git a/test/e2e/specs/setup-process.once.js b/test/e2e/specs/setup-process.once.js new file mode 100644 index 0000000..f1bdf2b --- /dev/null +++ b/test/e2e/specs/setup-process.once.js @@ -0,0 +1,55 @@ +import { test } from "@playwright/test"; +import { getSqliteDatabaseExists, login, screenshot, takeSqliteSnapshot } from "../util-test"; + +test.describe("Uptime Kuma Setup", () => { + + test.skip(() => getSqliteDatabaseExists(), "Must only run once per session"); + + test.afterEach(async ({ page }, testInfo) => { + await screenshot(testInfo, page); + }); + + /* + * Setup + */ + + test("setup sqlite", async ({ page }, testInfo) => { + await page.goto("./"); + await page.getByText("SQLite").click(); + await page.getByRole("button", { name: "Next" }).click(); + await screenshot(testInfo, page); + await page.waitForURL("/setup"); // ensures the server is ready to continue to the next test + }); + + test("setup admin", async ({ page }) => { + await page.goto("./"); + await page.getByPlaceholder("Username").click(); + await page.getByPlaceholder("Username").fill("admin"); + await page.getByPlaceholder("Username").press("Tab"); + await page.getByPlaceholder("Password", { exact: true }).fill("admin123"); + await page.getByPlaceholder("Password", { exact: true }).press("Tab"); + await page.getByPlaceholder("Repeat Password").fill("admin123"); + await page.getByRole("button", { name: "Create" }).click(); + }); + + /* + * All other tests should be run after setup + */ + + test("login", async ({ page }) => { + await page.goto("./dashboard"); + await login(page); + }); + + test("logout", async ({ page }) => { + await page.goto("./dashboard"); + await login(page); + await page.getByText("A", { exact: true }).click(); + await page.getByRole("button", { name: "Log out" }).click(); + }); + + test("take sqlite snapshot", async ({ page }) => { + await takeSqliteSnapshot(page); + }); + +}); diff --git a/test/e2e/specs/status-page.spec.js b/test/e2e/specs/status-page.spec.js new file mode 100644 index 0000000..f525dfc --- /dev/null +++ b/test/e2e/specs/status-page.spec.js @@ -0,0 +1,129 @@ +import { expect, test } from "@playwright/test"; +import { login, restoreSqliteSnapshot, screenshot } from "../util-test"; + +test.describe("Status Page", () => { + + test.beforeEach(async ({ page }) => { + await restoreSqliteSnapshot(page); + }); + + test("create and edit", async ({ page }, testInfo) => { + // Monitor + const monitorName = "Monitor for Status Page"; + const tagName = "Client"; + const tagValue = "Acme Inc"; + + // Status Page + const footerText = "This is footer text."; + const refreshInterval = 30; + const theme = "dark"; + const googleAnalyticsId = "G-123"; + const customCss = "body { background: rgb(0, 128, 128) !important; }"; + const descriptionText = "This is an example status page."; + const incidentTitle = "Example Outage Incident"; + const incidentContent = "Sample incident message."; + const groupName = "Example Group 1"; + + // Set up a monitor that can be added to the Status Page + await page.goto("./add"); + await login(page); + await expect(page.getByTestId("monitor-type-select")).toBeVisible(); + await page.getByTestId("monitor-type-select").selectOption("http"); + await page.getByTestId("friendly-name-input").fill(monitorName); + await page.getByTestId("url-input").fill("https://www.example.com/"); + await page.getByTestId("add-tag-button").click(); + await page.getByTestId("tag-name-input").fill(tagName); + await page.getByTestId("tag-value-input").fill(tagValue); + await page.getByTestId("tag-color-select").click(); // Vue-Multiselect component + await page.getByTestId("tag-color-select").getByRole("option", { name: "Orange" }).click(); + await page.getByTestId("tag-submit-button").click(); + await page.getByTestId("save-button").click(); + await page.waitForURL("/dashboard/*"); // wait for the monitor to be created + + // Create a new status page + await page.goto("./add-status-page"); + await screenshot(testInfo, page); + + await page.getByTestId("name-input").fill("Example"); + await page.getByTestId("slug-input").fill("example"); + await page.getByTestId("submit-button").click(); + await page.waitForURL("/status/example?edit"); // wait for the page to be created + + // Fill in some details + await page.getByTestId("description-input").fill(descriptionText); + await page.getByTestId("footer-text-input").fill(footerText); + await page.getByTestId("refresh-interval-input").fill(String(refreshInterval)); + await page.getByTestId("theme-select").selectOption(theme); + await page.getByTestId("show-tags-checkbox").uncheck(); + await page.getByTestId("show-powered-by-checkbox").uncheck(); + await page.getByTestId("show-certificate-expiry-checkbox").uncheck(); + await page.getByTestId("google-analytics-input").fill(googleAnalyticsId); + await page.getByTestId("custom-css-input").getByTestId("textarea").fill(customCss); // Prism + await expect(page.getByTestId("description-editable")).toHaveText(descriptionText); + await expect(page.getByTestId("custom-footer-editable")).toHaveText(footerText); + + // Add an incident + await page.getByTestId("create-incident-button").click(); + await page.getByTestId("incident-title").isEditable(); + await page.getByTestId("incident-title").fill(incidentTitle); + await page.getByTestId("incident-content-editable").fill(incidentContent); + await page.getByTestId("post-incident-button").click(); + + // Add a group + await page.getByTestId("add-group-button").click(); + await page.getByTestId("group-name").isEditable(); + await page.getByTestId("group-name").fill(groupName); + + // Add the monitor + await page.getByTestId("monitor-select").click(); // Vue-Multiselect component + await page.getByTestId("monitor-select").getByRole("option", { name: monitorName }).click(); + await expect(page.getByTestId("monitor")).toHaveCount(1); + await expect(page.getByTestId("monitor-name")).toContainText(monitorName); + + // Save the changes + await screenshot(testInfo, page); + await page.getByTestId("save-button").click(); + await expect(page.getByTestId("edit-sidebar")).toHaveCount(0); + + // Ensure changes are visible + await expect(page.getByTestId("incident")).toHaveCount(1); + await expect(page.getByTestId("incident-title")).toContainText(incidentTitle); + await expect(page.getByTestId("incident-content")).toContainText(incidentContent); + await expect(page.getByTestId("description")).toContainText(descriptionText); + await expect(page.getByTestId("group-name")).toContainText(groupName); + await expect(page.getByTestId("footer-text")).toContainText(footerText); + await expect(page.getByTestId("powered-by")).toHaveCount(0); + + await expect(page.getByTestId("update-countdown-text")).toContainText("00:"); + const updateCountdown = Number((await page.getByTestId("update-countdown-text").textContent()).match(/(\d+):(\d+)/)[2]); + expect(updateCountdown).toBeGreaterThanOrEqual(refreshInterval); // cant be certain when the timer will start, so ensure it's within expected range + expect(updateCountdown).toBeLessThanOrEqual(refreshInterval + 10); + + await expect(page.locator("body")).toHaveClass(theme); + expect(await page.locator("head").innerHTML()).toContain(googleAnalyticsId); + + const backgroundColor = await page.evaluate(() => window.getComputedStyle(document.body).backgroundColor); + expect(backgroundColor).toEqual("rgb(0, 128, 128)"); + + await screenshot(testInfo, page); + + // Flip the "Show Tags" and "Show Powered By" switches: + await page.getByTestId("edit-button").click(); + await expect(page.getByTestId("edit-sidebar")).toHaveCount(1); + await page.getByTestId("show-tags-checkbox").setChecked(true); + await page.getByTestId("show-powered-by-checkbox").setChecked(true); + + await screenshot(testInfo, page); + await page.getByTestId("save-button").click(); + + await expect(page.getByTestId("edit-sidebar")).toHaveCount(0); + await expect(page.getByTestId("powered-by")).toContainText("Powered by"); + await expect(page.getByTestId("monitor-tag")).toContainText(tagValue); + + await screenshot(testInfo, page); + }); + + // @todo Test certificate expiry + // @todo Test domain names + +}); diff --git a/test/e2e/util-test.js b/test/e2e/util-test.js new file mode 100644 index 0000000..f6af3cb --- /dev/null +++ b/test/e2e/util-test.js @@ -0,0 +1,62 @@ +const fs = require("fs"); +const path = require("path"); +const serverUrl = require("../../config/playwright.config.js").url; + +const dbPath = "./../../data/playwright-test/kuma.db"; + +/** + * @param {TestInfo} testInfo Test info + * @param {Page} page Page + * @returns {Promise<void>} + */ +export async function screenshot(testInfo, page) { + const screenshot = await page.screenshot(); + await testInfo.attach("screenshot", { + body: screenshot, + contentType: "image/png" + }); +} + +/** + * @param {Page} page Page + * @returns {Promise<void>} + */ +export async function login(page) { + // Login + await page.getByPlaceholder("Username").click(); + await page.getByPlaceholder("Username").fill("admin"); + await page.getByPlaceholder("Username").press("Tab"); + await page.getByPlaceholder("Password").fill("admin123"); + await page.getByLabel("Remember me").check(); + await page.getByRole("button", { name: "Log in" }).click(); + await page.isVisible("text=Add New Monitor"); +} + +/** + * Determines if the SQLite database has been created. This indicates setup has completed. + * @returns {boolean} True if exists + */ +export function getSqliteDatabaseExists() { + return fs.existsSync(path.resolve(__dirname, dbPath)); +} + +/** + * Makes a request to the server to take a snapshot of the SQLite database. + * @param {Page|null} page Page + * @returns {Promise<Response>} Promise of response from snapshot request. + */ +export async function takeSqliteSnapshot(page = null) { + if (page) { + return page.goto("./_e2e/take-sqlite-snapshot"); + } else { + return fetch(`${serverUrl}/_e2e/take-sqlite-snapshot`); + } +} + +/** + * Makes a request to the server to restore the snapshot of the SQLite database. + * @returns {Promise<Response>} Promise of response from restoration request. + */ +export async function restoreSqliteSnapshot() { + return fetch(`${serverUrl}/_e2e/restore-sqlite-snapshot`); +} diff --git a/test/prepare-test-server.js b/test/prepare-test-server.js new file mode 100644 index 0000000..1e0ed5c --- /dev/null +++ b/test/prepare-test-server.js @@ -0,0 +1,10 @@ +const fs = require("fs"); + +const path = "./data/test"; + +if (fs.existsSync(path)) { + fs.rmSync(path, { + recursive: true, + force: true, + }); +} diff --git a/test/test-radius.dockerfile b/test/test-radius.dockerfile new file mode 100644 index 0000000..3f577ed --- /dev/null +++ b/test/test-radius.dockerfile @@ -0,0 +1,13 @@ +# Container running a test radius server +# More instructions in https://github.com/louislam/uptime-kuma/pull/1635 + +FROM freeradius/freeradius-server:latest + +RUN mkdir -p /etc/raddb/mods-config/files/ + +RUN echo "client net {" > /etc/raddb/clients.conf +RUN echo " ipaddr = 172.17.0.0/16" >> /etc/raddb/clients.conf +RUN echo " secret = testing123" >> /etc/raddb/clients.conf +RUN echo "}" >> /etc/raddb/clients.conf + +RUN echo "bob Cleartext-Password := \"testpw\"" > /etc/raddb/mods-config/files/authorize |