mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 07:49:52 +03:00
add Unity unit tests
This commit is contained in:
@@ -1,4 +1,23 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/emsesp/EMS-ESP
|
||||
* Copyright 2020-2024 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <unity.h>
|
||||
|
||||
#include <emsesp.h>
|
||||
@@ -8,12 +27,28 @@
|
||||
|
||||
using namespace emsesp;
|
||||
|
||||
AsyncWebServer * webServer;
|
||||
ESP8266React * esp8266React;
|
||||
WebAPIService * webAPIService;
|
||||
AsyncWebServerRequest request;
|
||||
EMSESP application;
|
||||
FS dummyFS;
|
||||
AsyncWebServer * webServer;
|
||||
ESP8266React * esp8266React;
|
||||
WebAPIService * webAPIService;
|
||||
EMSESP application;
|
||||
FS dummyFS;
|
||||
|
||||
// forward declarations
|
||||
void run_tests();
|
||||
const char * call_url(const char * url);
|
||||
|
||||
// load the tests
|
||||
// this is generated from this file when compiled with -DUNITY_CREATE
|
||||
// copy the output to the test_api.h file
|
||||
#include "test_api.h" // generated test functions
|
||||
|
||||
// Unity's setup call - is called before each test
|
||||
void setUp() {
|
||||
}
|
||||
|
||||
// Unity's teardown call - is called after each test
|
||||
void tearDown() {
|
||||
}
|
||||
|
||||
// simulates a telegram straight from UART, but without the CRC which is added automatically
|
||||
void uart_telegram(const std::vector<uint8_t> & rx_data) {
|
||||
@@ -28,6 +63,7 @@ void uart_telegram(const std::vector<uint8_t> & rx_data) {
|
||||
EMSESP::incoming_telegram(data, i + 1);
|
||||
}
|
||||
|
||||
// simulates a telegram from a "string" of hex values
|
||||
void uart_telegram(const char * rx_data) {
|
||||
// since the telegram data is a const, make a copy. add 1 to grab the \0 EOS
|
||||
char telegram[(EMS_MAX_TELEGRAM_LENGTH * 3) + 1];
|
||||
@@ -66,25 +102,13 @@ void uart_telegram(const char * rx_data) {
|
||||
EMSESP::incoming_telegram(data, count + 2);
|
||||
}
|
||||
|
||||
// add an EMS device and regiser it
|
||||
void add_device(uint8_t device_id, uint8_t product_id) {
|
||||
uart_telegram({device_id, 0x0B, EMSdevice::EMS_TYPE_VERSION, 0, product_id, 1, 0});
|
||||
}
|
||||
|
||||
void setUp() {
|
||||
JsonDocument doc;
|
||||
JsonVariant json;
|
||||
|
||||
webServer = new AsyncWebServer(80);
|
||||
esp8266React = new ESP8266React(webServer, &dummyFS);
|
||||
webAPIService = new WebAPIService(webServer, esp8266React->getSecurityManager());
|
||||
|
||||
application.start(); // calls begin()
|
||||
|
||||
EMSESP::webCustomEntityService.test(); // custom entities
|
||||
EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the FS
|
||||
EMSESP::temperaturesensor_.test(); // add temperature sensors
|
||||
EMSESP::webSchedulerService.test(); // run scheduler tests, and conditions
|
||||
|
||||
// add our EMS test devices
|
||||
void add_devices() {
|
||||
//
|
||||
// boiler
|
||||
//
|
||||
@@ -114,84 +138,224 @@ void setUp() {
|
||||
EMSESP::rxservice_.loop();
|
||||
}
|
||||
|
||||
void tearDown() {
|
||||
}
|
||||
|
||||
void get_url(const char * url) {
|
||||
// call the endpoint and get the response, GET
|
||||
const char * call_url(const char * url) {
|
||||
AsyncWebServerRequest request;
|
||||
request.method(HTTP_GET);
|
||||
request.url(url);
|
||||
webAPIService->webAPIService(&request);
|
||||
|
||||
return webAPIService->getResponse();
|
||||
}
|
||||
|
||||
void test1() {
|
||||
get_url("/api/system");
|
||||
// call the endpoint and get the response, using a POST
|
||||
const char * call_url(const char * url, const char * data) {
|
||||
JsonDocument doc;
|
||||
JsonVariant json;
|
||||
|
||||
// escape strings with https://dolitools.com/text-tools/escape-unescape-string/
|
||||
auto response =
|
||||
"[{\"system\":{\"version\":\"3.7.0-dev.29\",\"uptime\":\"000+00:00:00.000\",\"uptimeSec\":0,\"resetReason\":\"Unknown / "
|
||||
"Unknown\"},\"network\":{\"network\":\"WiFi\",\"hostname\":\"ems-esp\",\"RSSI\":-23,\"TxPowerSetting\":0,\"staticIP\":false,\"lowBandwidth\":false,"
|
||||
"\"disableSleep\":false,\"enableMDNS\":true,\"enableCORS\":false},\"ntp\":{},\"mqtt\":{\"MQTTStatus\":\"disconnected\",\"MQTTPublishes\":0,"
|
||||
"\"MQTTQueued\":"
|
||||
"0,\"MQTTPublishFails\":0,\"MQTTConnects\":1,\"enabled\":true,\"clientID\":\"ems-esp\",\"keepAlive\":60,\"cleanSession\":false,\"entityFormat\":1,"
|
||||
"\"base\":"
|
||||
"\"ems-esp\",\"discoveryPrefix\":\"homeassistant\",\"discoveryType\":0,\"nestedFormat\":1,\"haEnabled\":true,\"mqttQos\":0,\"mqttRetain\":false,"
|
||||
"\"publishTimeHeartbeat\":60,\"publishTimeBoiler\":10,\"publishTimeThermostat\":10,\"publishTimeSolar\":10,\"publishTimeMixer\":10,"
|
||||
"\"publishTimeWater\":0,"
|
||||
"\"publishTimeOther\":10,\"publishTimeSensor\":10,\"publishSingle\":false,\"publish2command\":false,\"sendResponse\":false},\"syslog\":{\"enabled\":"
|
||||
"false},"
|
||||
"\"sensor\":{\"temperatureSensors\":2,\"temperatureSensorReads\":0,\"temperatureSensorFails\":0,\"analogSensors\":2,\"analogSensorReads\":0,"
|
||||
"\"analogSensorFails\":0},\"api\":{\"APICalls\":0,\"APIFails\":0},\"bus\":{\"busStatus\":\"connected\",\"busProtocol\":\"Buderus\","
|
||||
"\"busTelegramsReceived\":8,\"busReads\":0,\"busWrites\":0,\"busIncompleteTelegrams\":0,\"busReadsFailed\":0,\"busWritesFailed\":0,"
|
||||
"\"busRxLineQuality\":"
|
||||
"100,\"busTxLineQuality\":100},\"settings\":{\"boardProfile\":\"S32\",\"locale\":\"en\",\"txMode\":8,\"emsBusID\":11,\"showerTimer\":false,"
|
||||
"\"showerMinDuration\":180,\"showerAlert\":false,\"hideLed\":false,\"noTokenApi\":false,\"readonlyMode\":false,\"fahrenheit\":false,\"dallasParasite\":"
|
||||
"false,\"boolFormat\":1,\"boolDashboard\":1,\"enumFormat\":1,\"analogEnabled\":true,\"telnetEnabled\":true,\"maxWebLogBuffer\":50,\"webLogBuffer\":33,"
|
||||
"\"modbusEnabled\":false},\"devices\":[{\"type\":\"boiler\",\"name\":\"Custom "
|
||||
"Name!!\",\"deviceID\":\"0x08\",\"productID\":123,\"brand\":\"\",\"version\":\"01.00\",\"entities\":37,\"handlersReceived\":\"0x18\","
|
||||
"\"handlersFetched\":"
|
||||
"\"0x14 0x33\",\"handlersPending\":\"0xBF 0x10 0x11 0xC2 0x15 0x1C 0x19 0x1A 0x35 0x34 0x2A 0xD1 0xE3 0xE4 0xE5 0xE9 0x2E "
|
||||
"0x3B\"},{\"type\":\"thermostat\",\"name\":\"FW120\",\"deviceID\":\"0x10\",\"productID\":192,\"brand\":\"\",\"version\":\"01.00\",\"entities\":15,"
|
||||
"\"handlersReceived\":\"0x016F\",\"handlersFetched\":\"0x0170 0x0171\",\"handlersPending\":\"0xA3 0x06 0xA2 0x12 0x13 0x0172 0x0165 0x0168\"}]}]";
|
||||
deserializeJson(doc, data);
|
||||
json = doc.as<JsonVariant>();
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING(response, webAPIService->getResponse());
|
||||
AsyncWebServerRequest request;
|
||||
request.method(HTTP_POST);
|
||||
request.url(url);
|
||||
webAPIService->webAPIService(&request, json);
|
||||
|
||||
return webAPIService->getResponse();
|
||||
}
|
||||
|
||||
void test_custom1() {
|
||||
get_url("/api/custom");
|
||||
auto response = "[{\"test_custom\":0.00,\"test_read_only\":0.00,\"test_ram\":\"14\",\"seltemp\":\"14\"}]";
|
||||
TEST_ASSERT_EQUAL_STRING(response, webAPIService->getResponse());
|
||||
// capture the response and print it out as a test, auto-generates the test functions
|
||||
// use with -DUNITY_CREATE in the platformio build flags
|
||||
// only needs to be done once
|
||||
void capture(const char * url = nullptr) {
|
||||
static uint8_t count = 1;
|
||||
|
||||
if (count == 1) {
|
||||
Serial.println();
|
||||
Serial.println("// ---------- START - CUT HERE ----------");
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
if (url) {
|
||||
// call API, find and replace all double quotes with escaped quotes
|
||||
std::string escaped_response = call_url(url);
|
||||
|
||||
size_t pos = 0;
|
||||
while ((pos = escaped_response.find("\"", pos)) != std::string::npos) {
|
||||
escaped_response.replace(pos, 1, "\\\"");
|
||||
pos += 2;
|
||||
}
|
||||
|
||||
Serial.printf("void test_%d() {\n", count++);
|
||||
Serial.printf(" auto expected_response = \"%s\";\n", escaped_response.c_str());
|
||||
Serial.printf(" TEST_ASSERT_EQUAL_STRING(expected_response, call_url(\"%s\"));\n", url);
|
||||
Serial.println("}");
|
||||
Serial.println();
|
||||
} else {
|
||||
// no args means last call, create the run_tests function
|
||||
Serial.println("void run_tests() {");
|
||||
for (uint8_t i = 1; i < count; i++) {
|
||||
Serial.printf(" RUN_TEST(test_%d);\n", i);
|
||||
}
|
||||
Serial.println("}");
|
||||
Serial.println();
|
||||
Serial.println("// ---------- END - CUT HERE ----------");
|
||||
Serial.println();
|
||||
Serial.println();
|
||||
}
|
||||
}
|
||||
|
||||
void test_custom2() {
|
||||
get_url("/api/custom/seltemp");
|
||||
auto response = "[{\"name\":\"seltemp\",\"storage\":\"ram\",\"type\":\"number\",\"readable\":true,\"writeable\":true,\"visible\":true,\"value\":\"14\"}]";
|
||||
TEST_ASSERT_EQUAL_STRING(response, webAPIService->getResponse());
|
||||
// Functions for backup, just in case we don't have generated functions yet
|
||||
// void test1() {
|
||||
// TEST_ASSERT(expected_response != nullptr);
|
||||
// }
|
||||
// void run_tests() {
|
||||
// RUN_TEST(test1);
|
||||
// }
|
||||
|
||||
void manual_test1() {
|
||||
auto expected_response = "[{}]"; // empty is good
|
||||
char data[] = "{\"cmd\":\"send\",\"data\":\"0B 90 FF 13 01 01 B9 01\"}";
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/system", data));
|
||||
}
|
||||
|
||||
void manual_test2() {
|
||||
auto expected_response = "[{}]"; // empty is good
|
||||
char data[] = "{\"value\":12}";
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/thermostat/seltemp", data));
|
||||
}
|
||||
|
||||
void manual_test3() {
|
||||
auto expected_response = "[{}]"; // empty is good
|
||||
char data[] = "{\"device\":\"thermostat\", \"cmd\":\"hc2.seltemp\",\"value\":14}";
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api", data));
|
||||
}
|
||||
|
||||
void manual_test4() {
|
||||
auto expected_response = "[{}]"; // empty is good
|
||||
char data[] = "{\"entity\":\"seltemp\",\"value\":11}";
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/thermostat", data));
|
||||
}
|
||||
|
||||
void run_manual_tests() {
|
||||
RUN_TEST(manual_test1);
|
||||
RUN_TEST(manual_test2);
|
||||
RUN_TEST(manual_test3);
|
||||
RUN_TEST(manual_test4);
|
||||
}
|
||||
|
||||
// Main entry point
|
||||
int main() {
|
||||
webServer = new AsyncWebServer(80);
|
||||
esp8266React = new ESP8266React(webServer, &dummyFS);
|
||||
webAPIService = new WebAPIService(webServer, esp8266React->getSecurityManager());
|
||||
|
||||
application.start(); // calls begin()
|
||||
|
||||
EMSESP::webCustomEntityService.test(); // custom entities
|
||||
EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the FS
|
||||
EMSESP::temperaturesensor_.test(); // add temperature sensors
|
||||
EMSESP::webSchedulerService.test(); // run scheduler tests, and conditions
|
||||
|
||||
add_devices(); // add devices
|
||||
|
||||
#if defined(UNITY_CREATE)
|
||||
|
||||
// These tests should all pass....
|
||||
|
||||
capture("/api/boiler");
|
||||
capture("/api/boiler/commands");
|
||||
capture("/api/boiler/values");
|
||||
capture("/api/boiler/info");
|
||||
// capture("/api/boiler/entities"); // skipping since payload is too large
|
||||
capture("/api/boiler/comfort");
|
||||
capture("/api/boiler/comfort/value");
|
||||
capture("/api/boiler/comfort/fullname");
|
||||
capture("/api/boiler/outdoortemp");
|
||||
capture("/api/boiler/dhw/chargetype");
|
||||
capture("/api/boiler/dhw.chargetype/writeable");
|
||||
capture("/api/boiler/flamecurr/value");
|
||||
|
||||
// thermostat
|
||||
capture("/api/thermostat");
|
||||
capture("/api/thermostat/hc1/values");
|
||||
capture("/api/thermostat/hc1/seltemp");
|
||||
capture("/api/thermostat/hc2/seltemp");
|
||||
|
||||
// custom
|
||||
capture("/api/custom");
|
||||
capture("/api/custom/info");
|
||||
capture("/api/custom/seltemp");
|
||||
|
||||
// system
|
||||
capture("/api/system");
|
||||
capture("/api/system/info");
|
||||
capture("/api/system/settings/locale");
|
||||
capture("/api/system/fetch");
|
||||
capture("api/system/network/values");
|
||||
|
||||
// scheduler
|
||||
capture("/api/scheduler");
|
||||
capture("/api/scheduler/info");
|
||||
capture("/api/scheduler/test_scheduler");
|
||||
|
||||
// temperaturesensor
|
||||
capture("/api/temperaturesensor");
|
||||
capture("/api/temperaturesensor/info");
|
||||
capture("/api/temperaturesensor/test_sensor2");
|
||||
capture("/api/temperaturesensor/0B_0C0D_0E0F_1011");
|
||||
capture("/api/temperaturesensor/test_sensor2/value");
|
||||
|
||||
// analogsensor
|
||||
capture("/api/analogsensor");
|
||||
capture("/api/analogsensor/info");
|
||||
capture("/api/analogsensor/test_analog1");
|
||||
capture("/api/analogsensor/test_analog1/offset");
|
||||
|
||||
// these tests should all fail...
|
||||
capture("/api/boiler2");
|
||||
capture("/api/boiler/bad/value");
|
||||
capture("/api/boiler/comfort/valu");
|
||||
|
||||
// system
|
||||
capture("/api/system/settings/locale2");
|
||||
capture("/api/system/settings2");
|
||||
capture("/api/system/settings2/locale2");
|
||||
|
||||
|
||||
// scheduler
|
||||
capture("/api/scheduler/test_scheduler2");
|
||||
capture("/api/scheduler/test_scheduler/val");
|
||||
capture("/api/scheduler/test_scheduler2/val2");
|
||||
|
||||
// custom
|
||||
capture("/api/custom/seltemp2");
|
||||
capture("/api/custom/seltemp/val");
|
||||
|
||||
// temperaturesensor
|
||||
capture("/api/temperaturesensor/test_sensor20");
|
||||
capture("/api/temperaturesensor/0B_0C0D_0E0F_XXXX");
|
||||
capture("/api/temperaturesensor/test_sensor2/bad");
|
||||
|
||||
// analogsensor
|
||||
capture("/api/analogsensor/test_analog1/bad");
|
||||
capture("/api/analogsensor/test_analog10");
|
||||
capture("/api/analogsensor/test_analog10/bad2");
|
||||
|
||||
// **************************************************************************************************
|
||||
// Finish
|
||||
capture(); // always end with this, this will create the run_test() function
|
||||
#endif
|
||||
|
||||
// always run the tests
|
||||
UNITY_BEGIN();
|
||||
|
||||
RUN_TEST(test1);
|
||||
RUN_TEST(test_custom1);
|
||||
RUN_TEST(test_custom2);
|
||||
|
||||
// TODO add all the remaining tests from test.cpp "api3"
|
||||
// and Michaels' tests...
|
||||
|
||||
/*
|
||||
api/device
|
||||
api/device/info
|
||||
api/device/value
|
||||
api/device/entities
|
||||
api/device/commands
|
||||
api/thermostat/hc2/values (info/entities) to get single circuit info
|
||||
api/system/network/values (info/entities) to get single circuit info same as for ems devices
|
||||
api/device/name
|
||||
api/device/name/attribute
|
||||
api/device/circuit/name
|
||||
api/device/circuit/name/attribute
|
||||
*/
|
||||
|
||||
run_tests(); // execute the generated tests
|
||||
run_manual_tests(); // execute some other manual tests from this file
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user