mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-12-25 21:54:16 -08:00
The test suite now provides:
- Better test organization - More comprehensive coverage - Improved error handling - Better async cleanup - More realistic test scenarios
This commit is contained in:
parent
7668d3bd3e
commit
ee70e245f0
|
@ -3,41 +3,62 @@ const assert = require("node:assert");
|
||||||
const { HiveMQContainer } = require("@testcontainers/hivemq");
|
const { HiveMQContainer } = require("@testcontainers/hivemq");
|
||||||
const mqtt = require("mqtt");
|
const mqtt = require("mqtt");
|
||||||
const { MqttMonitorType } = require("../../server/monitor-types/mqtt");
|
const { MqttMonitorType } = require("../../server/monitor-types/mqtt");
|
||||||
const { UP, PENDING } = require("../../src/util");
|
const { UP, DOWN, PENDING } = require("../../src/util");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs an MQTT test with the
|
* Runs an MQTT test with the given parameters
|
||||||
* @param {string} mqttSuccessMessage the message that the monitor expects
|
* @param {object} options Test configuration options
|
||||||
* @param {null|"keyword"|"json-query"} mqttCheckType the type of check we perform
|
* @param {string} options.mqttSuccessMessage The message that the monitor expects
|
||||||
* @param {string} receivedMessage what message is recieved from the mqtt channel
|
* @param {null|"keyword"|"json-query"} options.mqttCheckType The type of check to perform
|
||||||
* @returns {Promise<Heartbeat>} the heartbeat produced by the check
|
* @param {string} options.receivedMessage Message received from the MQTT channel
|
||||||
|
* @param {string} options.jsonPath JSON path for json-query checks
|
||||||
|
* @param {string} options.topic MQTT topic to subscribe to
|
||||||
|
* @param {number} options.interval Monitor check interval
|
||||||
|
* @param {string} options.username MQTT username
|
||||||
|
* @param {string} options.password MQTT password
|
||||||
|
* @returns {Promise<heartbeat>} The heartbeat produced by the check
|
||||||
*/
|
*/
|
||||||
async function testMqtt(mqttSuccessMessage, mqttCheckType, receivedMessage) {
|
async function testMqtt({
|
||||||
|
mqttSuccessMessage,
|
||||||
|
mqttCheckType,
|
||||||
|
receivedMessage,
|
||||||
|
jsonPath = "firstProp",
|
||||||
|
topic = "test",
|
||||||
|
interval = 20,
|
||||||
|
username = null,
|
||||||
|
password = null
|
||||||
|
}) {
|
||||||
const hiveMQContainer = await new HiveMQContainer().start();
|
const hiveMQContainer = await new HiveMQContainer().start();
|
||||||
const connectionString = hiveMQContainer.getConnectionString();
|
const connectionString = hiveMQContainer.getConnectionString();
|
||||||
const mqttMonitorType = new MqttMonitorType();
|
const mqttMonitorType = new MqttMonitorType();
|
||||||
|
|
||||||
const monitor = {
|
const monitor = {
|
||||||
jsonPath: "firstProp", // always return firstProp for the json-query monitor
|
jsonPath,
|
||||||
hostname: connectionString.split(":", 2).join(":"),
|
hostname: connectionString.split(":", 2).join(":"),
|
||||||
mqttTopic: "test",
|
mqttTopic: topic,
|
||||||
port: connectionString.split(":")[2],
|
port: connectionString.split(":")[2],
|
||||||
mqttUsername: null,
|
mqttUsername: username,
|
||||||
mqttPassword: null,
|
mqttPassword: password,
|
||||||
interval: 20, // controls the timeout
|
interval,
|
||||||
mqttSuccessMessage: mqttSuccessMessage, // for keywords
|
mqttSuccessMessage,
|
||||||
expectedValue: mqttSuccessMessage, // for json-query
|
expectedValue: mqttSuccessMessage,
|
||||||
mqttCheckType: mqttCheckType,
|
mqttCheckType,
|
||||||
};
|
};
|
||||||
|
|
||||||
const heartbeat = {
|
const heartbeat = {
|
||||||
msg: "",
|
msg: "",
|
||||||
status: PENDING,
|
status: PENDING,
|
||||||
};
|
};
|
||||||
|
|
||||||
const testMqttClient = mqtt.connect(hiveMQContainer.getConnectionString());
|
const testMqttClient = mqtt.connect(hiveMQContainer.getConnectionString(), {
|
||||||
|
username,
|
||||||
|
password
|
||||||
|
});
|
||||||
|
|
||||||
testMqttClient.on("connect", () => {
|
testMqttClient.on("connect", () => {
|
||||||
testMqttClient.subscribe("test", (error) => {
|
testMqttClient.subscribe(topic, (error) => {
|
||||||
if (!error) {
|
if (!error) {
|
||||||
testMqttClient.publish("test", receivedMessage);
|
testMqttClient.publish(topic, receivedMessage);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -46,7 +67,7 @@ async function testMqtt(mqttSuccessMessage, mqttCheckType, receivedMessage) {
|
||||||
await mqttMonitorType.check(monitor, heartbeat, {});
|
await mqttMonitorType.check(monitor, heartbeat, {});
|
||||||
} finally {
|
} finally {
|
||||||
testMqttClient.end();
|
testMqttClient.end();
|
||||||
hiveMQContainer.stop();
|
await hiveMQContainer.stop();
|
||||||
}
|
}
|
||||||
return heartbeat;
|
return heartbeat;
|
||||||
}
|
}
|
||||||
|
@ -55,48 +76,171 @@ describe("MqttMonitorType", {
|
||||||
concurrency: true,
|
concurrency: true,
|
||||||
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64")
|
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64")
|
||||||
}, () => {
|
}, () => {
|
||||||
test("valid keywords (type=default)", async () => {
|
describe("Keyword Matching Tests", () => {
|
||||||
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-");
|
test("should match exact keyword (type=default)", async () => {
|
||||||
assert.strictEqual(heartbeat.status, UP);
|
const heartbeat = await testMqtt({
|
||||||
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
|
mqttSuccessMessage: "KEYWORD",
|
||||||
|
mqttCheckType: null,
|
||||||
|
receivedMessage: "KEYWORD"
|
||||||
|
});
|
||||||
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
|
assert.strictEqual(heartbeat.msg, "Topic: test; Message: KEYWORD");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should match keyword within message (type=default)", async () => {
|
||||||
|
const heartbeat = await testMqtt({
|
||||||
|
mqttSuccessMessage: "KEYWORD",
|
||||||
|
mqttCheckType: null,
|
||||||
|
receivedMessage: "-> KEYWORD <-"
|
||||||
|
});
|
||||||
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
|
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should fail on missing keyword (type=default)", async () => {
|
||||||
|
await assert.rejects(
|
||||||
|
testMqtt({
|
||||||
|
mqttSuccessMessage: "NOT_PRESENT",
|
||||||
|
mqttCheckType: null,
|
||||||
|
receivedMessage: "-> KEYWORD <-"
|
||||||
|
}),
|
||||||
|
new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle special characters in keyword", async () => {
|
||||||
|
const heartbeat = await testMqtt({
|
||||||
|
mqttSuccessMessage: "特殊文字",
|
||||||
|
mqttCheckType: "keyword",
|
||||||
|
receivedMessage: "Message: 特殊文字"
|
||||||
|
});
|
||||||
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("valid keywords (type=keyword)", async () => {
|
describe("JSON Query Tests", () => {
|
||||||
const heartbeat = await testMqtt("KEYWORD", "keyword", "-> KEYWORD <-");
|
test("should match simple JSON value", async () => {
|
||||||
assert.strictEqual(heartbeat.status, UP);
|
const heartbeat = await testMqtt({
|
||||||
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
|
mqttSuccessMessage: "present",
|
||||||
});
|
mqttCheckType: "json-query",
|
||||||
test("invalid keywords (type=default)", async () => {
|
receivedMessage: "{\"firstProp\":\"present\"}"
|
||||||
await assert.rejects(
|
});
|
||||||
testMqtt("NOT_PRESENT", null, "-> KEYWORD <-"),
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"),
|
assert.strictEqual(heartbeat.msg, "Message received, expected value is found");
|
||||||
);
|
});
|
||||||
|
|
||||||
|
test("should handle nested JSON paths", async () => {
|
||||||
|
const heartbeat = await testMqtt({
|
||||||
|
mqttSuccessMessage: "nested-value",
|
||||||
|
mqttCheckType: "json-query",
|
||||||
|
receivedMessage: "{\"parent\":{\"firstProp\":\"nested-value\"}}",
|
||||||
|
jsonPath: "parent.firstProp"
|
||||||
|
});
|
||||||
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should fail on missing JSON path", async () => {
|
||||||
|
await assert.rejects(
|
||||||
|
testMqtt({
|
||||||
|
mqttSuccessMessage: "value",
|
||||||
|
mqttCheckType: "json-query",
|
||||||
|
receivedMessage: "{}",
|
||||||
|
jsonPath: "nonexistent"
|
||||||
|
}),
|
||||||
|
/Message received but value is not equal to expected value/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should fail on invalid JSON", async () => {
|
||||||
|
await assert.rejects(
|
||||||
|
testMqtt({
|
||||||
|
mqttSuccessMessage: "value",
|
||||||
|
mqttCheckType: "json-query",
|
||||||
|
receivedMessage: "invalid-json"
|
||||||
|
}),
|
||||||
|
/Unexpected token/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle array values", async () => {
|
||||||
|
const heartbeat = await testMqtt({
|
||||||
|
mqttSuccessMessage: "item2",
|
||||||
|
mqttCheckType: "json-query",
|
||||||
|
receivedMessage: "{\"firstProp\":[\"item1\",\"item2\",\"item3\"]}",
|
||||||
|
jsonPath: "firstProp[1]"
|
||||||
|
});
|
||||||
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("invalid keyword (type=keyword)", async () => {
|
describe("Authentication Tests", () => {
|
||||||
await assert.rejects(
|
test("should handle successful authentication", async () => {
|
||||||
testMqtt("NOT_PRESENT", "keyword", "-> KEYWORD <-"),
|
const heartbeat = await testMqtt({
|
||||||
new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"),
|
mqttSuccessMessage: "auth-success",
|
||||||
);
|
mqttCheckType: "keyword",
|
||||||
|
receivedMessage: "auth-success",
|
||||||
|
username: "testuser",
|
||||||
|
password: "testpass"
|
||||||
|
});
|
||||||
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle failed authentication", async () => {
|
||||||
|
await assert.rejects(
|
||||||
|
testMqtt({
|
||||||
|
mqttSuccessMessage: "irrelevant",
|
||||||
|
mqttCheckType: "keyword",
|
||||||
|
receivedMessage: "irrelevant",
|
||||||
|
username: "invalid",
|
||||||
|
password: "invalid"
|
||||||
|
}),
|
||||||
|
/Authentication failed/
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
test("valid json-query", async () => {
|
|
||||||
// works because the monitors' jsonPath is hard-coded to "firstProp"
|
describe("Error Handling Tests", () => {
|
||||||
const heartbeat = await testMqtt("present", "json-query", "{\"firstProp\":\"present\"}");
|
test("should handle connection timeout", async () => {
|
||||||
assert.strictEqual(heartbeat.status, UP);
|
await assert.rejects(
|
||||||
assert.strictEqual(heartbeat.msg, "Message received, expected value is found");
|
testMqtt({
|
||||||
});
|
mqttSuccessMessage: "timeout",
|
||||||
test("invalid (because query fails) json-query", async () => {
|
mqttCheckType: "keyword",
|
||||||
// works because the monitors' jsonPath is hard-coded to "firstProp"
|
receivedMessage: "timeout",
|
||||||
await assert.rejects(
|
interval: 1
|
||||||
testMqtt("[not_relevant]", "json-query", "{}"),
|
}),
|
||||||
new Error("Message received but value is not equal to expected value, value was: [undefined]"),
|
/Timeout/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test("invalid (because successMessage fails) json-query", async () => {
|
|
||||||
// works because the monitors' jsonPath is hard-coded to "firstProp"
|
test("should handle invalid topic format", async () => {
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
testMqtt("[wrong_success_messsage]", "json-query", "{\"firstProp\":\"present\"}"),
|
testMqtt({
|
||||||
new Error("Message received but value is not equal to expected value, value was: [present]")
|
mqttSuccessMessage: "invalid",
|
||||||
);
|
mqttCheckType: "keyword",
|
||||||
|
receivedMessage: "invalid",
|
||||||
|
topic: "invalid/#/topic"
|
||||||
|
}),
|
||||||
|
/Invalid topic/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle disconnection", async () => {
|
||||||
|
const hiveMQContainer = await new HiveMQContainer().start();
|
||||||
|
const heartbeat = { status: PENDING,
|
||||||
|
msg: "" };
|
||||||
|
const monitor = new MqttMonitorType();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await hiveMQContainer.stop();
|
||||||
|
await monitor.check({
|
||||||
|
hostname: hiveMQContainer.getConnectionString().split(":")[0],
|
||||||
|
port: hiveMQContainer.getConnectionString().split(":")[2],
|
||||||
|
mqttTopic: "test"
|
||||||
|
}, heartbeat, {});
|
||||||
|
assert.fail("Should have thrown an error");
|
||||||
|
} catch (error) {
|
||||||
|
assert.ok(error.message.includes("connect"));
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue