From ee70e245f00fbec162c60d4ab07cefff83b9d92a Mon Sep 17 00:00:00 2001 From: Zaid-maker Date: Wed, 27 Nov 2024 12:17:11 +0500 Subject: [PATCH] The test suite now provides: - Better test organization - More comprehensive coverage - Improved error handling - Better async cleanup - More realistic test scenarios --- test/backend-test/test-mqtt.js | 258 +++++++++++++++++++++++++-------- 1 file changed, 201 insertions(+), 57 deletions(-) diff --git a/test/backend-test/test-mqtt.js b/test/backend-test/test-mqtt.js index 450310298..d26c7b9a0 100644 --- a/test/backend-test/test-mqtt.js +++ b/test/backend-test/test-mqtt.js @@ -3,41 +3,62 @@ 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 { UP, DOWN, 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} the heartbeat produced by the check + * Runs an MQTT test with the given parameters + * @param {object} options Test configuration options + * @param {string} options.mqttSuccessMessage The message that the monitor expects + * @param {null|"keyword"|"json-query"} options.mqttCheckType The type of check to perform + * @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} 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 connectionString = hiveMQContainer.getConnectionString(); const mqttMonitorType = new MqttMonitorType(); + const monitor = { - jsonPath: "firstProp", // always return firstProp for the json-query monitor + jsonPath, hostname: connectionString.split(":", 2).join(":"), - mqttTopic: "test", + mqttTopic: topic, port: connectionString.split(":")[2], - mqttUsername: null, - mqttPassword: null, - interval: 20, // controls the timeout - mqttSuccessMessage: mqttSuccessMessage, // for keywords - expectedValue: mqttSuccessMessage, // for json-query - mqttCheckType: mqttCheckType, + mqttUsername: username, + mqttPassword: password, + interval, + mqttSuccessMessage, + expectedValue: mqttSuccessMessage, + mqttCheckType, }; + const heartbeat = { msg: "", status: PENDING, }; - const testMqttClient = mqtt.connect(hiveMQContainer.getConnectionString()); + const testMqttClient = mqtt.connect(hiveMQContainer.getConnectionString(), { + username, + password + }); + testMqttClient.on("connect", () => { - testMqttClient.subscribe("test", (error) => { + testMqttClient.subscribe(topic, (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, {}); } finally { testMqttClient.end(); - hiveMQContainer.stop(); + await hiveMQContainer.stop(); } return heartbeat; } @@ -55,48 +76,171 @@ 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 <-"); + describe("Keyword Matching Tests", () => { + test("should match exact keyword (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 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 () => { - 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 <-"), - ); + describe("JSON Query Tests", () => { + test("should match simple JSON value", async () => { + const heartbeat = await testMqtt({ + mqttSuccessMessage: "present", + mqttCheckType: "json-query", + receivedMessage: "{\"firstProp\":\"present\"}" + }); + assert.strictEqual(heartbeat.status, UP); + 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 () => { - await assert.rejects( - testMqtt("NOT_PRESENT", "keyword", "-> KEYWORD <-"), - new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"), - ); + describe("Authentication Tests", () => { + test("should handle successful authentication", async () => { + const heartbeat = await testMqtt({ + 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" - 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]") - ); + + describe("Error Handling Tests", () => { + test("should handle connection timeout", async () => { + await assert.rejects( + testMqtt({ + mqttSuccessMessage: "timeout", + mqttCheckType: "keyword", + receivedMessage: "timeout", + interval: 1 + }), + /Timeout/ + ); + }); + + test("should handle invalid topic format", async () => { + await assert.rejects( + testMqtt({ + 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")); + } + }); }); });