diff --git a/CMakeLists.txt b/CMakeLists.txt index 728bae0..39c1e2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,12 @@ set(CMAKE_AUTOUIC ON) # Add src to include path for relative includes include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src) +# Enable testing framework +enable_testing() + +# Add subdirectory for tests +add_subdirectory(tests) + # Create executable add_executable(SHinterface src/sensors/mqttsensorsource.h src/sensors/mqttsensorsource.cpp) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..6beb2eb --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,140 @@ +cmake_minimum_required(VERSION 4.0) + +# Enable testing +enable_testing() + +# Find Qt packages for tests +find_package(Qt6 COMPONENTS Core Gui Widgets Multimedia Test REQUIRED) + +# Define common sources needed by all tests - include only what's actually needed for basic testing +set(COMMON_TEST_SOURCES + ../src/items/item.h + ../src/items/item.cpp + ../src/sensors/sensor.h + ../src/sensors/sensor.cpp + ../src/programmode.h + ../src/programmode.cpp + ../src/microcontroller.h + ../src/microcontroller.cpp + ../src/actors/actor.h + ../src/actors/actor.cpp + ../src/actors/factoractor.h + ../src/actors/factoractor.cpp + ../src/actors/polynomalactor.h + ../src/actors/polynomalactor.cpp + ../src/actors/sensoractor.h + ../src/actors/sensoractor.cpp + ../src/actors/timeractor.h + ../src/actors/timeractor.cpp + ../src/items/relay.h + ../src/items/relay.cpp + ../src/items/messageitem.h + ../src/items/messageitem.cpp + ../src/items/systemitem.h + ../src/items/systemitem.cpp + ../src/items/auxitem.h + ../src/items/auxitem.cpp + ../src/items/poweritem.h + ../src/items/poweritem.cpp + ../src/items/rgbitem.h + ../src/items/rgbitem.cpp + ../src/actors/alarmtime.h + ../src/actors/alarmtime.cpp + ../src/actors/regulator.h + ../src/actors/regulator.cpp + ../src/items/itemsource.h + ../src/items/itemsource.cpp + ../src/items/itemstore.h + ../src/items/itemstore.cpp + ../src/items/itemloadersource.h + ../src/items/itemloadersource.cpp +) + +# Add test executables - compile all needed sources into each test +add_executable(test_item unit/items/test_item.cpp ${COMMON_TEST_SOURCES}) +add_executable(test_sensor unit/sensors/test_sensor.cpp ${COMMON_TEST_SOURCES}) +add_executable(test_actor unit/actors/test_actor.cpp ${COMMON_TEST_SOURCES}) +add_executable(test_itemstore unit/items/test_itemstore.cpp ${COMMON_TEST_SOURCES}) +add_executable(test_itemloadersource unit/items/test_itemloadersource.cpp ${COMMON_TEST_SOURCES}) + +# Link libraries for test_item +target_link_libraries(test_item + Qt6::Core + Qt6::Gui + Qt6::Widgets + Qt6::Multimedia + Qt6::Test +) + +# Include paths for source files +target_include_directories(test_item PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../src + ${Qt6Gui_PRIVATE_INCLUDE_DIRS} +) + +# Link libraries for test_sensor +target_link_libraries(test_sensor + Qt6::Core + Qt6::Gui + Qt6::Widgets + Qt6::Multimedia + Qt6::Test +) + +# Include paths for source files +target_include_directories(test_sensor PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../src + ${Qt6Gui_PRIVATE_INCLUDE_DIRS} +) + +# Link libraries for test_actor +target_link_libraries(test_actor + Qt6::Core + Qt6::Gui + Qt6::Widgets + Qt6::Multimedia + Qt6::Test +) + +# Include paths for source files +target_include_directories(test_actor PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../src + ${Qt6Gui_PRIVATE_INCLUDE_DIRS} +) + +# Link libraries for test_itemstore +target_link_libraries(test_itemstore + Qt6::Core + Qt6::Gui + Qt6::Widgets + Qt6::Multimedia + Qt6::Test +) + +# Include paths for source files +target_include_directories(test_itemstore PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../src + ${Qt6Gui_PRIVATE_INCLUDE_DIRS} +) + +# Link libraries for test_itemloadersource +target_link_libraries(test_itemloadersource + Qt6::Core + Qt6::Gui + Qt6::Widgets + Qt6::Multimedia + Qt6::Test +) + +# Include paths for source files +target_include_directories(test_itemloadersource PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../src + ${Qt6Gui_PRIVATE_INCLUDE_DIRS} +) + +# Add tests to CTest +add_test(NAME test_item COMMAND test_item) +add_test(NAME test_sensor COMMAND test_sensor) +add_test(NAME test_actor COMMAND test_actor) +add_test(NAME test_itemstore COMMAND test_itemstore) +add_test(NAME test_itemloadersource COMMAND test_itemloadersource) diff --git a/tests/unit/actors/test_actor.cpp b/tests/unit/actors/test_actor.cpp new file mode 100644 index 0000000..cf42d32 --- /dev/null +++ b/tests/unit/actors/test_actor.cpp @@ -0,0 +1,333 @@ +#include +#include "actors/actor.h" +#include "actors/timeractor.h" +#include "actors/sensoractor.h" + +class TestActor : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase() + { + // Setup for all tests + } + + void testActorCreation() + { + Actor actor; + + // Actor should be active by default + QVERIFY(actor.isActive()); + + // Actor should not be exhausted by default + QVERIFY(!actor.isExausted()); + } + + void testActorActivation() + { + Actor actor; + + // Test makeActive/makeInactive + actor.makeActive(); + QVERIFY(actor.isActive()); + + actor.makeInactive(); + QVERIFY(!actor.isActive()); + } + + void testActorSetActive() + { + Actor actor; + + // Test setActive + actor.setActive(1); + QVERIFY(actor.isActive()); + + actor.setActive(0); + QVERIFY(!actor.isActive()); + } + + void testActorTriggerValue() + { + Actor actor; + + // Default trigger value should be 0 + QVERIFY(actor.getTriggerValue() == 0); + + // Set trigger value + actor.setTriggerValue(1); + QVERIFY(actor.getTriggerValue() == 1); + + actor.setTriggerValue(255); + QVERIFY(actor.getTriggerValue() == 255); + } + + void testActorActionName() + { + Actor actor; + + // Default trigger value is 0, so action name should be "off" + QVERIFY(actor.actionName() == "off"); + + // Set trigger value to 1 + actor.setTriggerValue(1); + QVERIFY(actor.actionName() == "on"); + + // Set trigger value to something else + actor.setTriggerValue(5); + QVERIFY(actor.actionName() == "value to 5"); + } + + void testActorJsonSerialization() + { + Actor actor; + actor.setTriggerValue(1); + actor.makeInactive(); + + QJsonObject json; + actor.store(json); + + // Verify JSON contents + QVERIFY(json.contains("Active")); + QVERIFY(json.contains("Exausted")); + QVERIFY(json.contains("TriggerValue")); + + QVERIFY(json["Active"].toBool() == false); + QVERIFY(json["Exausted"].toBool() == false); + QVERIFY(json["TriggerValue"].toInt() == 1); + } + + void testActorJsonDeserialization() + { + QJsonObject json; + json["Active"] = false; + json["Exausted"] = true; + json["TriggerValue"] = 5; + + Actor actor; + actor.load(json); + + QVERIFY(actor.isActive() == false); + QVERIFY(actor.isExausted() == true); + QVERIFY(actor.getTriggerValue() == 5); + } + + void testActorCreateActor() + { + // Test creating different actor types + std::shared_ptr alarmActor = Actor::createActor("Alarm"); + QVERIFY(alarmActor != nullptr); + + std::shared_ptr sensorActor = Actor::createActor("Sensor"); + QVERIFY(sensorActor != nullptr); + + std::shared_ptr timerActor = Actor::createActor("Timer"); + QVERIFY(timerActor != nullptr); + + std::shared_ptr regulatorActor = Actor::createActor("Regulator"); + QVERIFY(regulatorActor != nullptr); + + std::shared_ptr polynomalActor = Actor::createActor("Polynomal"); + QVERIFY(polynomalActor != nullptr); + + std::shared_ptr factorActor = Actor::createActor("MultiFactor"); + QVERIFY(factorActor != nullptr); + + std::shared_ptr genericActor = Actor::createActor("Actor"); + QVERIFY(genericActor != nullptr); + + // Test unknown type returns nullptr + std::shared_ptr unknownActor = Actor::createActor("UnknownType"); + QVERIFY(unknownActor == nullptr); + } + + void testActorLoadActor() + { + QJsonObject json; + json["Type"] = "Timer"; + json["Active"] = true; + json["Exausted"] = false; + json["TriggerValue"] = 1; + json["Timeout"] = 5000; + + std::shared_ptr actor = Actor::loadActor(json); + QVERIFY(actor != nullptr); + + // Verify the actor was loaded with correct values + QVERIFY(actor->isActive()); + QVERIFY(!actor->isExausted()); + QVERIFY(actor->getTriggerValue() == 1); + } + + void testTimerActorCreation() + { + TimerActor actor(60); + + // Default timeout should be 60 seconds + QVERIFY(actor.getTimeout() == 60); + } + + void testTimerActorSetTimeout() + { + TimerActor actor(60); + + actor.setTimeout(120); + QVERIFY(actor.getTimeout() == 120); + + actor.setTimeout(5); + QVERIFY(actor.getTimeout() == 5); + } + + void testTimerActorJsonSerialization() + { + TimerActor actor(120); + + QJsonObject json; + actor.store(json); + + // Verify JSON contents + QVERIFY(json.contains("Type")); + QVERIFY(json.contains("Timeout")); + + QVERIFY(json["Type"].toString() == "Timer"); + QVERIFY(json["Timeout"].toInt() == 120000); // Converted to milliseconds + } + + void testTimerActorJsonDeserialization() + { + QJsonObject json; + json["Type"] = "Timer"; + json["Timeout"] = 30000; // 30 seconds in milliseconds + json["Active"] = true; + json["Exausted"] = false; + json["TriggerValue"] = 1; + + TimerActor actor; + actor.load(json, false); + + // Timeout should be 30 seconds + QVERIFY(actor.getTimeout() == 30); + } + + void testTimerActorGetName() + { + TimerActor actor(60); + + QString name = actor.getName(); + QVERIFY(name.contains("60")); + } + + void testSensorActorCreation() + { + Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.0, "temp_sensor"); + SensorActor actor(sensor); + + // Verify sensor was set correctly + QVERIFY(actor.getSensor().type == Sensor::TYPE_TEMPERATURE); + QVERIFY(actor.getSensor().id == 1); + } + + void testSensorActorDefaultCreation() + { + SensorActor actor; + + // Default sensor should be dummy + QVERIFY(actor.getSensor().type == Sensor::TYPE_DUMMY); + } + + void testSensorActorSetSensor() + { + SensorActor actor; + + Sensor newSensor(Sensor::TYPE_HUMIDITY, 2, 60.0, "humidity_sensor"); + actor.setSensor(newSensor); + + QVERIFY(actor.getSensor().type == Sensor::TYPE_HUMIDITY); + QVERIFY(actor.getSensor().id == 2); + } + + void testSensorActorSetThreshold() + { + SensorActor actor; + + actor.setThreshold(25.0); + QVERIFY(actor.getThreshold() == 25.0); + } + + void testSensorActorSetSlope() + { + SensorActor actor; + + actor.setSloap(SensorActor::SLOPE_UP); + QVERIFY(actor.getSloap() == SensorActor::SLOPE_UP); + + actor.setSloap(SensorActor::SLOPE_DOWN); + QVERIFY(actor.getSloap() == SensorActor::SLOPE_DOWN); + + actor.setSloap(SensorActor::SLOPE_BOTH); + QVERIFY(actor.getSloap() == SensorActor::SLOPE_BOTH); + } + + void testSensorActorJsonSerialization() + { + Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.0, "temp_sensor"); + SensorActor actor(sensor); + actor.setThreshold(30.0); + actor.setSloap(SensorActor::SLOPE_UP); + + QJsonObject json; + actor.store(json); + + // Verify JSON contents + QVERIFY(json.contains("Type")); + QVERIFY(json.contains("Threshold")); + QVERIFY(json.contains("Sloap")); + QVERIFY(json.contains("SensorType")); + + QVERIFY(json["Type"].toString() == "Sensor"); + QVERIFY(json["Threshold"].toDouble() == 30.0); + QVERIFY(json["Sloap"].toInt() == SensorActor::SLOPE_UP); + } + + void testSensorActorJsonDeserialization() + { + QJsonObject json; + json["Type"] = "Sensor"; + json["Threshold"] = 25.5; + json["Sloap"] = SensorActor::SLOPE_DOWN; + json["SensorType"] = Sensor::TYPE_HUMIDITY; + json["SensorId"] = 3; + json["SensorField"] = 65.0; + json["SensorName"] = "humidity"; + json["Active"] = true; + json["Exausted"] = false; + json["TriggerValue"] = 1; + + SensorActor actor; + actor.load(json, false); + + QVERIFY(actor.getThreshold() == 25.5); + QVERIFY(actor.getSloap() == SensorActor::SLOPE_DOWN); + } + + void testSensorActorGetName() + { + Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.0, "temp_sensor"); + SensorActor actor(sensor); + actor.setThreshold(30.0); + + QString name = actor.getName(); + QVERIFY(name.contains("temp_sensor")); + QVERIFY(name.contains("30")); + } + + void cleanupTestCase() + { + // Cleanup after all tests + } +}; + +QTEST_APPLESS_MAIN(TestActor) + +#include "test_actor.moc" \ No newline at end of file diff --git a/tests/unit/items/test_item.cpp b/tests/unit/items/test_item.cpp new file mode 100644 index 0000000..b4ad369 --- /dev/null +++ b/tests/unit/items/test_item.cpp @@ -0,0 +1,225 @@ +#include +#include "items/item.h" +#include "items/itemstore.h" +#include "items/itemsource.h" +#include "items/itemloadersource.h" + +class TestItem : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase() + { + // Setup for all tests + } + + void testItemCreation() + { + Item item(0, "test_item", 0); + QCOMPARE(item.getName(), QString("test_item")); + } + + void testOverrideFunctionality() + { + Item item(0, "test_item", 0); + + // Test override on/off + item.setOverride(true); + QVERIFY(item.getOverride()); + + item.setOverride(false); + QVERIFY(!item.getOverride()); + } + + void testItemValueTypes() + { + // Test default value type is BOOL + Item item(1, "test_item", 0); + QCOMPARE(item.getValueType(), ITEM_VALUE_BOOL); + + // Test setting and getting value + item.setValueData(1); + QCOMPARE(item.getValue(), static_cast(1)); + + item.setValueData(0); + QCOMPARE(item.getValue(), static_cast(0)); + } + + void testItemId() + { + Item item(42, "test_item", 0); + QCOMPARE(item.id(), static_cast(42)); + } + + void testItemGroupName() + { + Item item(1, "test_item", 0); + QCOMPARE(item.getGroupName(), QString("All")); // Default group + + item.setGroupName("Living Room"); + QCOMPARE(item.getGroupName(), QString("Living Room")); + } + + void testItemHidden() + { + Item item(1, "test_item", 0); + QVERIFY(!item.isHidden()); + + item.setHidden(true); + QVERIFY(item.isHidden()); + + item.setHidden(false); + QVERIFY(!item.isHidden()); + } + + void testItemLoaded() + { + Item item(1, "test_item", 0); + QVERIFY(!item.getLoaded()); + + item.setLoaded(true); + QVERIFY(item.getLoaded()); + } + + void testItemJsonSerialization() + { + Item item(42, "test_item", 1); + item.setGroupName("TestGroup"); + + QJsonObject json; + item.store(json); + + // Verify JSON contents + QCOMPARE(json["ItemId"].toDouble(), 42.0); + // Note: Name is only stored if changes.name is true + // Value is only stored if changes.value is true + } + + void testItemJsonDeserialization() + { + QJsonObject json; + json["ItemId"] = 100; + json["Name"] = "loaded_item"; + json["Value"] = 1; + json["GroupName"] = "Bedroom"; + + Item item; + item.load(json); + + // Note: load() uses loadWithChanges which requires preserve=false to apply changes + // The id should be set + // The name should be set (since preserve defaults to false) + } + + void testItemUpdateRequest() + { + Item item(1, "test_item", 0); + + // Test creating update request + ItemUpdateRequest request = item.createValueUpdateRequest(ITEM_UPDATE_USER, false); + + QVERIFY(request.type == ITEM_UPDATE_USER); + // Note: changes is not automatically set by createValueUpdateRequest + // The caller is expected to set the changes they want + QVERIFY(request.newActors.empty()); + } + + void testItemUpdateRequestWithActors() + { + Item item(1, "test_item", 0); + + // Test creating update request with actors + ItemUpdateRequest request = item.createValueUpdateRequest(ITEM_UPDATE_USER, true); + + QVERIFY(request.type == ITEM_UPDATE_USER); + // With actors=true, newActors should be populated + // Note: changes.actors is set by the caller, not by createValueUpdateRequest + } + + void testItemHasActors() + { + Item item(1, "test_item", 0); + + QVERIFY(!item.hasActors()); + + // Note: Adding actors requires a valid Actor pointer + } + + void testItemRemoveAllActors() + { + Item item(1, "test_item", 0); + + item.removeAllActors(); + QVERIFY(!item.hasActors()); + } + + void testItemSetActorsActive() + { + Item item(1, "test_item", 0); + + // Should not crash when no actors + item.setActorsActive(true); + item.setActorsActive(false); + } + + void testItemDataChangeDetection() + { + ItemData data1(1, "item1", 0); + ItemData data2(1, "item1", 1); + + // Test value change detection + ItemFieldChanges changes(true); + QVERIFY(data1.hasChanged(data2, changes)); + + // Test no change + changes = ItemFieldChanges(false); + QVERIFY(!data1.hasChanged(data2, changes)); + } + + void testItemDataNameChange() + { + ItemData data1(1, "item1", 0); + ItemData data2(1, "item2", 0); + + ItemFieldChanges changes; + changes.name = true; + + QVERIFY(data1.hasChanged(data2, changes)); + } + + void testItemDataGroupNameChange() + { + ItemData data1(1, "item1", 0); + data1.setGroupName("Group1"); + + ItemData data2(1, "item1", 0); + data2.setGroupName("Group2"); + + ItemFieldChanges changes; + changes.groupName = true; + + QVERIFY(data1.hasChanged(data2, changes)); + } + + void testItemFieldChanges() + { + ItemFieldChanges changes(false); + QVERIFY(changes.isNone()); + + changes.value = true; + QVERIFY(!changes.isNone()); + + ItemFieldChanges allChanges(true); + QVERIFY(!allChanges.isNone()); + } + + void cleanupTestCase() + { + // Cleanup after all tests + } +}; + +QTEST_APPLESS_MAIN(TestItem) + +#include "test_item.moc" diff --git a/tests/unit/items/test_itemloadersource.cpp b/tests/unit/items/test_itemloadersource.cpp new file mode 100644 index 0000000..156edc4 --- /dev/null +++ b/tests/unit/items/test_itemloadersource.cpp @@ -0,0 +1,240 @@ +#include +#include "items/itemloadersource.h" +#include "items/item.h" + +class TestItemLoaderSource : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase() + { + // Setup for all tests + } + + void testItemLoaderSourceCreation() + { + ItemLoaderSource source; + + // Should be created with empty JSON + QVERIFY(true); // No crash + } + + void testItemLoaderSourceCreationWithJson() + { + QJsonObject json; + json["Items"] = QJsonArray(); + + ItemLoaderSource source(json); + + // Should be created with JSON + QVERIFY(true); // No crash + } + + void testItemLoaderSourceRefreshEmpty() + { + ItemLoaderSource source; + + // Should not crash with empty JSON + source.refresh(); + + // No items should be emitted + QVERIFY(true); + } + + void testItemLoaderSourceRefreshWithItems() + { + QJsonObject json; + QJsonArray itemsArray; + + QJsonObject item1; + item1["Type"] = "Item"; + item1["ItemId"] = 1; + item1["Name"] = "test_item1"; + item1["Value"] = 0; + itemsArray.append(item1); + + QJsonObject item2; + item2["Type"] = "Item"; + item2["ItemId"] = 2; + item2["Name"] = "test_item2"; + item2["Value"] = 1; + itemsArray.append(item2); + + json["Items"] = itemsArray; + + ItemLoaderSource source(json); + + // Capture the gotItems signal + std::vector capturedItems; + connect(&source, &ItemLoaderSource::gotItems, + [&capturedItems](std::vector items) { + capturedItems = items; + }); + + source.refresh(); + + // Should have 2 items + QVERIFY(capturedItems.size() == 2); + } + + void testItemLoaderSourceRefreshWithRelay() + { + QJsonObject json; + QJsonArray itemsArray; + + QJsonObject relay; + relay["Type"] = "Relay"; + relay["ItemId"] = 100; + relay["Name"] = "test_relay"; + relay["Value"] = 0; + relay["Id"] = 1; + relay["Address"] = 0x2000; + itemsArray.append(relay); + + json["Items"] = itemsArray; + + ItemLoaderSource source(json); + + // Capture the gotItems signal + std::vector capturedItems; + connect(&source, &ItemLoaderSource::gotItems, + [&capturedItems](std::vector items) { + capturedItems = items; + }); + + source.refresh(); + + // Should have 1 item + QVERIFY(capturedItems.size() == 1); + } + + void testItemLoaderSourceUpdateJson() + { + ItemLoaderSource source; + + // Initial JSON + QJsonObject json1; + QJsonArray items1; + QJsonObject item1; + item1["Type"] = "Item"; + item1["ItemId"] = 1; + item1["Name"] = "item1"; + items1.append(item1); + json1["Items"] = items1; + + source.updateJson(json1); + + // Update JSON + QJsonObject json2; + QJsonArray items2; + QJsonObject item2; + item2["Type"] = "Item"; + item2["ItemId"] = 2; + item2["Name"] = "item2"; + items2.append(item2); + json2["Items"] = items2; + + source.updateJson(json2); + + // Should not crash + QVERIFY(true); + } + + void testItemLoaderSourceRefreshWithInvalidItems() + { + QJsonObject json; + QJsonArray itemsArray; + + // Add invalid item (no Type) + QJsonObject item1; + item1["ItemId"] = 1; + itemsArray.append(item1); + + json["Items"] = itemsArray; + + ItemLoaderSource source(json); + + // Capture the gotItems signal + std::vector capturedItems; + connect(&source, &ItemLoaderSource::gotItems, + [&capturedItems](std::vector items) { + capturedItems = items; + }); + + source.refresh(); + + // Should handle invalid item gracefully (may return 0 items) + QVERIFY(capturedItems.size() == 0 || capturedItems.size() == 1); + } + + void testItemLoaderSourceRefreshWithMessageItem() + { + QJsonObject json; + QJsonArray itemsArray; + + QJsonObject messageItem; + messageItem["Type"] = "Message"; + messageItem["ItemId"] = 200; + messageItem["Name"] = "alert_item"; + messageItem["Value"] = 0; + messageItem["Message"] = "Test message"; + itemsArray.append(messageItem); + + json["Items"] = itemsArray; + + ItemLoaderSource source(json); + + // Capture the gotItems signal + std::vector capturedItems; + connect(&source, &ItemLoaderSource::gotItems, + [&capturedItems](std::vector items) { + capturedItems = items; + }); + + source.refresh(); + + // Should have 1 item + QVERIFY(capturedItems.size() == 1); + } + + void testItemLoaderSourceRefreshWithSystemItem() + { + QJsonObject json; + QJsonArray itemsArray; + + QJsonObject systemItem; + systemItem["Type"] = "System"; + systemItem["ItemId"] = 300; + systemItem["Name"] = "system_item"; + systemItem["Value"] = 0; + systemItem["OnCommand"] = "echo on"; + systemItem["OffCommand"] = "echo off"; + itemsArray.append(systemItem); + + json["Items"] = itemsArray; + + ItemLoaderSource source(json); + + // Capture the gotItems signal + std::vector capturedItems; + connect(&source, &ItemLoaderSource::gotItems, + [&capturedItems](std::vector items) { + capturedItems = items; + }); + + source.refresh(); + + // Should have 1 item + QVERIFY(capturedItems.size() == 1); + } + + void cleanupTestCase() + { + // Cleanup after all tests + } +}; + +QTEST_APPLESS_MAIN(TestItemLoaderSource) + +#include "test_itemloadersource.moc" \ No newline at end of file diff --git a/tests/unit/items/test_itemstore.cpp b/tests/unit/items/test_itemstore.cpp new file mode 100644 index 0000000..79d16c4 --- /dev/null +++ b/tests/unit/items/test_itemstore.cpp @@ -0,0 +1,255 @@ +#include +#include "items/itemstore.h" +#include "items/item.h" + +class TestItemStore : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase() + { + // Setup for all tests + } + + void testItemStoreCreation() + { + ItemStore store; + + // Should start empty + QVERIFY(store.getItems()->empty()); + } + + void testItemStoreAddItem() + { + ItemStore store; + + std::shared_ptr item(new Item(1, "test_item", 0)); + + ItemAddRequest request; + request.type = ITEM_UPDATE_USER; + request.payload = item; + request.changes = ItemFieldChanges(true); + + store.addItem(request); + + // Item should be added + QVERIFY(store.getItems()->size() == 1); + } + + void testItemStoreAddMultipleItems() + { + ItemStore store; + + std::shared_ptr item1(new Item(1, "item1", 0)); + std::shared_ptr item2(new Item(2, "item2", 1)); + + std::vector requests; + + ItemAddRequest request1; + request1.type = ITEM_UPDATE_USER; + request1.payload = item1; + request1.changes = ItemFieldChanges(true); + requests.push_back(request1); + + ItemAddRequest request2; + request2.type = ITEM_UPDATE_USER; + request2.payload = item2; + request2.changes = ItemFieldChanges(true); + requests.push_back(request2); + + store.addItems(requests); + + // Both items should be added + QVERIFY(store.getItems()->size() == 2); + } + + void testItemStoreGetItem() + { + ItemStore store; + + std::shared_ptr item(new Item(42, "test_item", 0)); + + ItemAddRequest request; + request.type = ITEM_UPDATE_USER; + request.payload = item; + request.changes = ItemFieldChanges(true); + + store.addItem(request); + + // Get item by id + std::shared_ptr found = store.getItem(42); + QVERIFY(found != nullptr); + QVERIFY(found->getName() == "test_item"); + + // Get non-existent item + std::shared_ptr notFound = store.getItem(999); + QVERIFY(notFound == nullptr); + } + + void testItemStoreRemoveItem() + { + ItemStore store; + + std::shared_ptr item(new Item(1, "test_item", 0)); + + ItemAddRequest request; + request.type = ITEM_UPDATE_USER; + request.payload = item; + request.changes = ItemFieldChanges(true); + + store.addItem(request); + QVERIFY(store.getItems()->size() == 1); + + // Remove item + ItemData itemData(1, "test_item", 0); + store.removeItem(itemData); + + QVERIFY(store.getItems()->empty()); + } + + void testItemStoreClear() + { + ItemStore store; + + // Add multiple items + for (int i = 0; i < 5; i++) { + std::shared_ptr item(new Item(i, "item" + QString::number(i), 0)); + ItemAddRequest request; + request.type = ITEM_UPDATE_USER; + request.payload = item; + request.changes = ItemFieldChanges(true); + store.addItem(request); + } + + QVERIFY(store.getItems()->size() == 5); + + // Clear all items + store.clear(); + + QVERIFY(store.getItems()->empty()); + } + + void testItemStoreUpdateItem() + { + ItemStore store; + + std::shared_ptr item(new Item(1, "test_item", 0)); + + ItemAddRequest request; + request.type = ITEM_UPDATE_USER; + request.payload = item; + request.changes = ItemFieldChanges(true); + + store.addItem(request); + + // Update item value + ItemUpdateRequest update; + update.type = ITEM_UPDATE_USER; + ItemData updatedData(1, "test_item", 1); // value = 1 + update.payload = updatedData; + update.changes.value = true; + + store.updateItem(update); + + // Verify value was updated + std::shared_ptr found = store.getItem(1); + QVERIFY(found->getValue() == 1); + } + + void testItemStoreUpdateMultipleItems() + { + ItemStore store; + + // Add items + for (int i = 0; i < 3; i++) { + std::shared_ptr item(new Item(i, "item" + QString::number(i), 0)); + ItemAddRequest request; + request.type = ITEM_UPDATE_USER; + request.payload = item; + request.changes = ItemFieldChanges(true); + store.addItem(request); + } + + // Update multiple items + std::vector updates; + + for (int i = 0; i < 3; i++) { + ItemUpdateRequest update; + update.type = ITEM_UPDATE_USER; + ItemData data(i, "item" + QString::number(i), 1); + update.payload = data; + update.changes.value = true; + updates.push_back(update); + } + + store.updateItems(updates); + + // Verify all values were updated + for (int i = 0; i < 3; i++) { + std::shared_ptr found = store.getItem(i); + QVERIFY(found->getValue() == 1); + } + } + + void testItemStoreJsonSerialization() + { + ItemStore store; + + // Add items + for (int i = 0; i < 2; i++) { + std::shared_ptr item(new Item(i, "item" + QString::number(i), i)); + item->setGroupName("TestGroup"); + ItemAddRequest request; + request.type = ITEM_UPDATE_USER; + request.payload = item; + request.changes = ItemFieldChanges(true); + store.addItem(request); + } + + // Serialize to JSON + QJsonObject json; + store.store(json); + + // Verify JSON contains items array + QVERIFY(json.contains("Items")); + QVERIFY(json["Items"].toArray().size() == 2); + } + + void testItemStoreReplaceItems() + { + ItemStore store; + + // Add initial items + std::shared_ptr item1(new Item(1, "item1", 0)); + ItemAddRequest request1; + request1.type = ITEM_UPDATE_USER; + request1.payload = item1; + request1.changes = ItemFieldChanges(true); + store.addItem(request1); + + QVERIFY(store.getItems()->size() == 1); + + // Replace with new items + std::vector> newItems; + newItems.push_back(std::shared_ptr(new Item(2, "new_item1", 0))); + newItems.push_back(std::shared_ptr(new Item(3, "new_item2", 1))); + + store.replaceItems(newItems); + + // Should have new items + QVERIFY(store.getItems()->size() == 2); + + // Old item should be removed + QVERIFY(store.getItem(1) == nullptr); + } + + void cleanupTestCase() + { + // Cleanup after all tests + } +}; + +QTEST_APPLESS_MAIN(TestItemStore) + +#include "test_itemstore.moc" \ No newline at end of file diff --git a/tests/unit/sensors/test_sensor.cpp b/tests/unit/sensors/test_sensor.cpp new file mode 100644 index 0000000..7ff4505 --- /dev/null +++ b/tests/unit/sensors/test_sensor.cpp @@ -0,0 +1,230 @@ +#include +#include "sensors/sensor.h" + +class TestSensor : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase() + { + // Setup for all tests + } + + void testSensorCreation() + { + Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 0.0, "test_sensor"); + + QCOMPARE(sensor.type, Sensor::TYPE_TEMPERATURE); + QCOMPARE(sensor.name, QString("test_sensor")); + } + + void testSensorData() + { + Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 0.0, "temp_sensor"); + + // Test setting and getting values + sensor.field = 25.0; + QCOMPARE(sensor.field, 25.0); + } + + void testSensorTypes() + { + // Test different sensor types + Sensor doorSensor(Sensor::TYPE_DOOR, 1); + Sensor tempSensor(Sensor::TYPE_TEMPERATURE, 2); + Sensor humiditySensor(Sensor::TYPE_HUMIDITY, 3); + + QCOMPARE(doorSensor.type, Sensor::TYPE_DOOR); + QCOMPARE(tempSensor.type, Sensor::TYPE_TEMPERATURE); + QCOMPARE(humiditySensor.type, Sensor::TYPE_HUMIDITY); + } + + void testSensorStringParsing() + { + // Test parsing sensor from string (format: "SENSOR TYPE: X ID: Y FIELD: Z TIME: T") + // This tests the sensorFromString function + QString sensorStr = "SENSOR TYPE: 1 ID: 5 FIELD: 250 TIME: 1234567890"; + Sensor sensor = Sensor::sensorFromString(sensorStr); + + // The function should parse the type, id, and field + // Note: temperature and humidity values are divided by 10 + QVERIFY(sensor.type != Sensor::TYPE_DUMMY || sensor.field != 0); + } + + void testSensorStringParsingInvalid() + { + // Test parsing invalid string returns dummy sensor + QString invalidStr = "invalid data"; + Sensor sensor = Sensor::sensorFromString(invalidStr); + + // Should return a dummy sensor with hidden=true + QVERIFY(sensor.hidden); + } + + void testSensorToString() + { + Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.5, "test_sensor"); + QString str = sensor.toString(); + + // Should contain type, id, and field info + QVERIFY(str.contains("SENSOR TYPE")); + QVERIFY(str.contains("ID:")); + QVERIFY(str.contains("FIELD:")); + } + + void testSensorJsonSerialization() + { + Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.0, "test_sensor"); + + QJsonObject json; + sensor.store(json); + + // Verify JSON contents + QVERIFY(json.contains("Type")); + QVERIFY(json.contains("SensorType")); + QVERIFY(json.contains("Id")); + QVERIFY(json.contains("Field")); + QVERIFY(json.contains("Name")); + QVERIFY(json.contains("Unit")); + + // Check values + QVERIFY(json["Type"].toString() == "Sensor"); + QVERIFY(json["SensorType"].toInt() == Sensor::TYPE_TEMPERATURE); + QVERIFY(json["Id"].toInt() == 1); + QVERIFY(json["Field"].toDouble() == 25.0); + QVERIFY(json["Name"].toString() == "test_sensor"); + } + + void testSensorJsonDeserialization() + { + QJsonObject json; + json["SensorType"] = Sensor::TYPE_HUMIDITY; + json["Id"] = 5; + json["Field"] = 60.5; + json["Name"] = "humidity_sensor"; + json["Hidden"] = false; + + Sensor sensor(json); + + QVERIFY(sensor.type == Sensor::TYPE_HUMIDITY); + QVERIFY(sensor.id == 5); + QVERIFY(sensor.field == 60.5); + QVERIFY(sensor.name == "humidity_sensor"); + QVERIFY(!sensor.hidden); + } + + void testSensorEquality() + { + Sensor sensor1(Sensor::TYPE_TEMPERATURE, 1, 25.0); + Sensor sensor2(Sensor::TYPE_TEMPERATURE, 1, 30.0); + Sensor sensor3(Sensor::TYPE_TEMPERATURE, 2, 25.0); + + // Same type and id means equal + QVERIFY(sensor1 == sensor2); + + // Different id means not equal + QVERIFY(sensor1 != sensor3); + } + + void testSensorUpdateSeen() + { + Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.0); + QDateTime before = sensor.lastSeen; + + // Wait a tiny bit and update + sensor.updateSeen(); + + QVERIFY(sensor.lastSeen >= before); + } + + void testSensorGenerateName() + { + // Test auto-generated names for different sensor types + Sensor tempSensor(Sensor::TYPE_TEMPERATURE, 1); + QVERIFY(tempSensor.name.contains("Temperature")); + + Sensor doorSensor(Sensor::TYPE_DOOR, 2); + QVERIFY(doorSensor.name.contains("Door")); + + Sensor buttonSensor(Sensor::TYPE_BUTTON, 3); + QVERIFY(buttonSensor.name.contains("Button")); + + Sensor humiditySensor(Sensor::TYPE_HUMIDITY, 4); + QVERIFY(humiditySensor.name.contains("Humidity")); + } + + void testSensorGetUnit() + { + Sensor tempSensor(Sensor::TYPE_TEMPERATURE, 1); + QVERIFY(tempSensor.getUnit() == "°C"); + + Sensor humiditySensor(Sensor::TYPE_HUMIDITY, 1); + QVERIFY(humiditySensor.getUnit() == "%"); + + Sensor pressureSensor(Sensor::TYPE_PRESSURE, 1); + QVERIFY(pressureSensor.getUnit() == "hPa"); + + Sensor brightnessSensor(Sensor::TYPE_BRIGHTNESS, 1); + QVERIFY(brightnessSensor.getUnit() == "lx"); + + Sensor co2Sensor(Sensor::TYPE_CO2, 1); + QVERIFY(co2Sensor.getUnit() == "ppm"); + + Sensor vocSensor(Sensor::TYPE_TOTAL_VOC, 1); + QVERIFY(vocSensor.getUnit() == "ppb"); + } + + void testSensorHidden() + { + Sensor sensor(Sensor::TYPE_TEMPERATURE, 1, 25.0, "test", false); + QVERIFY(!sensor.hidden); + + Sensor hiddenSensor(Sensor::TYPE_TEMPERATURE, 2, 25.0, "test", true); + QVERIFY(hiddenSensor.hidden); + } + + void testSensorAllTypes() + { + // Test all sensor types can be created + Sensor door(Sensor::TYPE_DOOR, 1); + Sensor temp(Sensor::TYPE_TEMPERATURE, 1); + Sensor humidity(Sensor::TYPE_HUMIDITY, 1); + Sensor pressure(Sensor::TYPE_PRESSURE, 1); + Sensor brightness(Sensor::TYPE_BRIGHTNESS, 1); + Sensor button(Sensor::TYPE_BUTTON, 1); + Sensor adc(Sensor::TYPE_ADC, 1); + Sensor co2(Sensor::TYPE_CO2, 1); + Sensor pm25(Sensor::TYPE_PM25, 1); + Sensor voc(Sensor::TYPE_TOTAL_VOC, 1); + Sensor lowBattery(Sensor::TYPE_LOWBATTERY, 1); + Sensor occupancy(Sensor::TYPE_OCUPANCY, 1); + Sensor sunAlt(Sensor::TYPE_SUN_ALTITUDE, 1); + Sensor audio(Sensor::TYPE_AUDIO_OUTPUT, 1); + + // All should be valid + QVERIFY(door.type == Sensor::TYPE_DOOR); + QVERIFY(temp.type == Sensor::TYPE_TEMPERATURE); + QVERIFY(humidity.type == Sensor::TYPE_HUMIDITY); + QVERIFY(pressure.type == Sensor::TYPE_PRESSURE); + QVERIFY(brightness.type == Sensor::TYPE_BRIGHTNESS); + QVERIFY(button.type == Sensor::TYPE_BUTTON); + QVERIFY(adc.type == Sensor::TYPE_ADC); + QVERIFY(co2.type == Sensor::TYPE_CO2); + QVERIFY(pm25.type == Sensor::TYPE_PM25); + QVERIFY(voc.type == Sensor::TYPE_TOTAL_VOC); + QVERIFY(lowBattery.type == Sensor::TYPE_LOWBATTERY); + QVERIFY(occupancy.type == Sensor::TYPE_OCUPANCY); + QVERIFY(sunAlt.type == Sensor::TYPE_SUN_ALTITUDE); + QVERIFY(audio.type == Sensor::TYPE_AUDIO_OUTPUT); + } + + void cleanupTestCase() + { + // Cleanup after all tests + } +}; + +QTEST_APPLESS_MAIN(TestSensor) + +#include "test_sensor.moc"