#include #include #include #include #include #include #include #include #include #include "items/mqttitem.h" #include "mqttclient.h" #include "programmode.h" class TestMqttItem : public QObject { Q_OBJECT private slots: void initTestCase() { // Setup for all tests // Try to load config and connect to MQTT broker if configured QString settingsPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/shinterface.json"; QJsonObject json; if (QFile::exists(settingsPath)) { QFile file(settingsPath); if (file.open(QIODevice::ReadOnly)) { QByteArray data = file.readAll(); file.close(); QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(data, &error); if (error.error == QJsonParseError::NoError) { json = doc.object(); } } } QJsonObject mqttJson = json["Mqtt"].toObject(); QString host = mqttJson["Host"].toString(); int port = mqttJson["Port"].toInt(1883); // If MQTT is configured with a host, try to connect static std::shared_ptr mqttClient; if (!host.isEmpty()) { qDebug() << "MQTT configured:" << host << port; mqttClient = std::make_shared(); mqttClient->start(mqttJson); // Give it a moment to connect QTest::qWait(1000); // Check if connected or connecting auto qClient = mqttClient->getClient(); if (qClient && (qClient->state() == QMqttClient::Connected || qClient->state() == QMqttClient::Connecting)) { qDebug() << "MQTT connected/connecting, using client"; MqttItem::client = mqttClient; } else { qDebug() << "MQTT connection failed, using UI_ONLY mode"; programMode = PROGRAM_MODE_UI_ONLY; } } else { qDebug() << "No MQTT host configured, using UI_ONLY mode"; programMode = PROGRAM_MODE_UI_ONLY; } } void testMqttItemCreation() { MqttItem item("test_mqtt", 0); QCOMPARE(item.getName(), QString("test_mqtt")); QVERIFY(item.getTopic().isEmpty()); QVERIFY(item.getValueKey() == "state"); } void testMqttItemSetTopic() { MqttItem item("test_mqtt", 0); item.setTopic("my_device"); item.setValueKey("state"); QVERIFY(item.getTopic() == "my_device"); QVERIFY(item.getValueKey() == "state"); } void testMqttItemSetValueType() { MqttItem item("test_mqtt", 0); // Default should be BOOL QVERIFY(item.getValueType() == ITEM_VALUE_BOOL); // Set to UINT item.setValueType(ITEM_VALUE_UINT); QVERIFY(item.getValueType() == ITEM_VALUE_UINT); // Set to ENUM item.setValueType(ITEM_VALUE_ENUM); QVERIFY(item.getValueType() == ITEM_VALUE_ENUM); } void testMqttItemValueNames() { MqttItem item("test_mqtt", 0); // Initially empty QVERIFY(item.getValueNames().empty()); // Set value names std::vector names = {"off", "heat", "cool"}; item.setValueNames(names); auto storedNames = item.getValueNames(); QVERIFY(storedNames.size() == 3); QVERIFY(storedNames[0] == "off"); QVERIFY(storedNames[1] == "heat"); QVERIFY(storedNames[2] == "cool"); } void testValueNameConversion() { MqttItem item("test_mqtt", 0); // Set value names for enum std::vector names = {"off", "heat", "cool", "auto"}; item.setValueNames(names); item.setValueType(ITEM_VALUE_ENUM); // Test name to index QVERIFY(item.valueNameToIndex("heat") == 1); QVERIFY(item.valueNameToIndex("cool") == 2); QVERIFY(item.valueNameToIndex("unknown") == -1); // Test index to name QVERIFY(item.indexToValueName(0) == "off"); QVERIFY(item.indexToValueName(3) == "auto"); QVERIFY(item.indexToValueName(99).isEmpty()); // Out of bounds } void testSetFromExposeBinary() { MqttItem item("test_mqtt", 0); QJsonObject expose; expose["type"] = "binary"; expose["property"] = "state"; expose["value_on"] = "ON"; expose["value_off"] = "OFF"; item.setFromExpose(expose); QVERIFY(item.getValueType() == ITEM_VALUE_BOOL); QVERIFY(item.getValueKey() == "state"); QVERIFY(item.getValueOn() == "ON"); QVERIFY(item.getValueOff() == "OFF"); } void testSetFromExposeNumeric() { MqttItem item("test_mqtt", 0); QJsonObject expose; expose["type"] = "numeric"; expose["property"] = "brightness"; expose["value_min"] = 0; expose["value_max"] = 254; expose["value_step"] = 1; item.setFromExpose(expose); QVERIFY(item.getValueType() == ITEM_VALUE_UINT); QVERIFY(item.getValueKey() == "brightness"); QVERIFY(item.getValueMin() == 0); QVERIFY(item.getValueMax() == 254); QVERIFY(item.getValueStep() == 1); } void testSetFromExposeEnum() { MqttItem item("test_mqtt", 0); QJsonObject expose; expose["type"] = "enum"; expose["property"] = "system_mode"; expose["values"] = QJsonArray{"off", "heat", "cool", "auto"}; item.setFromExpose(expose); QVERIFY(item.getValueType() == ITEM_VALUE_ENUM); QVERIFY(item.getValueKey() == "system_mode"); auto names = item.getValueNames(); QVERIFY(names.size() == 4); QVERIFY(names[0] == "off"); QVERIFY(names[1] == "heat"); QVERIFY(names[2] == "cool"); QVERIFY(names[3] == "auto"); } void testJsonSerialization() { MqttItem item("test_mqtt", 1); item.setTopic("my_device"); item.setValueKey("state"); item.setValueOn("ON"); item.setValueOff("OFF"); QJsonObject json; item.store(json); QVERIFY(json["Type"] == "Mqtt"); QVERIFY(json["Topic"] == "my_device"); QVERIFY(json["ValueKey"] == "state"); QVERIFY(json["ValueOn"] == "ON"); QVERIFY(json["ValueOff"] == "OFF"); } void testJsonDeserialization() { QJsonObject json; json["Type"] = "Mqtt"; json["ItemId"] = 100; json["Name"] = "loaded_mqtt"; json["Topic"] = "test_device"; json["ValueKey"] = "state"; json["ValueOn"] = "ON"; json["ValueOff"] = "OFF"; json["Value"] = 1; MqttItem item; item.load(json); QVERIFY(item.getTopic() == "test_device"); QVERIFY(item.getValueKey() == "state"); QVERIFY(item.getValueOn() == "ON"); QVERIFY(item.getValueOff() == "OFF"); } void testLoadExposeFromDevice() { // Create item with specific topic and valueKey MqttItem item("test", 0); item.setTopic("0xa4c138ef510950e3"); item.setValueKey("system_mode"); // Simulate device data from zigbee2mqtt/bridge/devices QJsonObject device; device["friendly_name"] = "0xa4c138ef510950e3"; device["ieee_address"] = "0xa4c138ef510950e3"; QJsonObject definition; definition["model"] = "TS0601_thermostat"; definition["vendor"] = "Tuya"; definition["description"] = "Thermostat"; QJsonArray exposes; // Binary expose QJsonObject stateExpose; stateExpose["type"] = "binary"; stateExpose["property"] = "state"; stateExpose["value_on"] = "ON"; stateExpose["value_off"] = "OFF"; exposes.append(stateExpose); // Enum expose - the one we're looking for QJsonObject systemModeExpose; systemModeExpose["type"] = "enum"; systemModeExpose["property"] = "system_mode"; systemModeExpose["values"] = QJsonArray{"off", "heat", "cool", "auto"}; exposes.append(systemModeExpose); // Numeric expose QJsonObject tempExpose; tempExpose["type"] = "numeric"; tempExpose["property"] = "current_temperature"; tempExpose["value_min"] = 0; tempExpose["value_max"] = 100; exposes.append(tempExpose); definition["exposes"] = exposes; device["definition"] = definition; // Call the private method via public API - we need to test the logic // Since loadExposeFromDevice is private, we test via setFromExpose QJsonObject enumExpose; enumExpose["type"] = "enum"; enumExpose["property"] = "system_mode"; enumExpose["values"] = QJsonArray{"off", "heat", "cool", "auto"}; item.setFromExpose(enumExpose); QVERIFY(item.getValueType() == ITEM_VALUE_ENUM); QVERIFY(item.getValueNames().size() == 4); } void cleanupTestCase() { // Cleanup after all tests } }; QTEST_APPLESS_MAIN(TestMqttItem) #include "test_mqttitem.moc"