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:
Zaid-maker 2024-11-27 12:17:11 +05:00
parent 7668d3bd3e
commit ee70e245f0

View file

@ -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"));
}
});
}); });
}); });