Audit of the paths that drop or refuse something. registerEvent() dropped an eleventh handler and a second one for the same name without a word: the name now gets the new handler, an eleventh name and a call without a name or a function print an error. A device that cannot be announced printed two lines, one without its id; it prints one. setLogLevel() above ALEX2ESP_LOG_MAX prints an error that names the build flag. "the handler sent no answer" is an error for a handler of onDirective() only: a 1.x handler may answer from a later loop(), its silence is a line at DEBUG. getDevice() returning nullptr and printMemoryInfo() are documented; keywords.txt has the 2.0 names and loses three that were removed. basicLight: static RAM 30,520 B, flash 333,989 B (+600); ALEX2ESP_LOG_MAX=0 30,496 / 327,953, =3 30,520 / 334,477; five examples, log0 and log3 build with 0 warnings. Host tests: 135 (dispatch 11 -> 16). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
539 lines
21 KiB
C++
539 lines
21 KiB
C++
// Host tests of dispatch: which device and which capability a directive reaches, what the handler is given, what
|
|
// a device holds, and what the device answers itself (src/AlexaDevice.cpp); and of the log level (src/AlexaLog.cpp).
|
|
// The transport of the devices is a fake that keeps what would have been published.
|
|
// pio test -e native
|
|
#include <unity.h>
|
|
#include <ArduinoJson.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <string>
|
|
#include <vector>
|
|
#include "AlexaDevice.h"
|
|
#include "AlexaLog.h"
|
|
|
|
class FakeTransport : public AlexaTransport
|
|
{
|
|
public:
|
|
struct Message
|
|
{
|
|
std::string topic;
|
|
std::string json;
|
|
};
|
|
std::vector<Message> sent;
|
|
|
|
AlexaSendResult publish(const char *topic, JsonDocument &doc) override
|
|
{
|
|
Message message;
|
|
message.topic = topic;
|
|
serializeJson(doc, message.json);
|
|
sent.push_back(message);
|
|
return AlexaSendResult::OK;
|
|
}
|
|
|
|
void timestamp(char *buffer, size_t size) override { snprintf(buffer, size, "2026-09-28T13:05:09Z"); }
|
|
};
|
|
|
|
class CapturedLog : public Print
|
|
{
|
|
public:
|
|
std::string text;
|
|
|
|
size_t write(const uint8_t *buffer, size_t size) override
|
|
{
|
|
text.append(reinterpret_cast<const char *>(buffer), size);
|
|
return size;
|
|
}
|
|
};
|
|
|
|
static void assertLogged(const CapturedLog &log, const char *expected)
|
|
{
|
|
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(log.text.c_str(), expected), log.text.c_str());
|
|
}
|
|
|
|
// What the handler of the tests was given, copied: the texts of a directive end with the handler
|
|
struct Seen
|
|
{
|
|
int calls;
|
|
AlexaDevice *device;
|
|
AlexaCapability *capability;
|
|
AlexaInterfaceType type;
|
|
std::string ns;
|
|
std::string name;
|
|
std::string instance;
|
|
std::string correlationToken;
|
|
int rangeValue;
|
|
bool reportState;
|
|
};
|
|
|
|
static Seen seen;
|
|
static bool handlerAnswers;
|
|
|
|
static void onDirective(AlexaDirective &directive)
|
|
{
|
|
seen.calls++;
|
|
seen.device = directive.device;
|
|
seen.capability = directive.capability;
|
|
seen.type = directive.type;
|
|
seen.ns = directive.ns;
|
|
seen.name = directive.name;
|
|
seen.instance = directive.instance;
|
|
seen.correlationToken = directive.correlationToken;
|
|
seen.rangeValue = directive.payload["rangeValue"] | -1;
|
|
seen.reportState = directive.isReportState();
|
|
if (!handlerAnswers)
|
|
{
|
|
return;
|
|
}
|
|
if (directive.isReportState())
|
|
{
|
|
directive.stateReport().AddPowerControllerProp(PowerController::OFF).send();
|
|
}
|
|
else
|
|
{
|
|
directive.response().AddPowerControllerProp(directive.is("TurnOn") ? PowerController::ON : PowerController::OFF).send();
|
|
}
|
|
}
|
|
|
|
static int eventCalls;
|
|
static int reportStateCalls;
|
|
static AlexaInterfaceType eventType;
|
|
|
|
static void onEvent(const JsonDocument &, const AlexaInterfaceType &type)
|
|
{
|
|
eventCalls++;
|
|
eventType = type;
|
|
}
|
|
|
|
static void onReportState(const JsonDocument &, const AlexaInterfaceType &)
|
|
{
|
|
reportStateCalls++;
|
|
}
|
|
|
|
// The directive as Alex2MQTT publishes it on <root>/<endpointId>/alexaDirective
|
|
static void directiveFor(JsonDocument &message, const char *endpointId, const char *ns, const char *name,
|
|
const char *instance = nullptr)
|
|
{
|
|
message.clear();
|
|
message["header"]["namespace"] = ns;
|
|
message["header"]["name"] = name;
|
|
message["header"]["payloadVersion"] = "3";
|
|
message["header"]["messageId"] = "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4";
|
|
message["header"]["correlationToken"] = "token-of-the-directive";
|
|
if (instance != nullptr)
|
|
{
|
|
message["header"]["instance"] = instance;
|
|
}
|
|
message["endpoint"]["endpointId"] = endpointId;
|
|
message["payload"].to<JsonObject>();
|
|
}
|
|
|
|
static void assertErrorResponse(const FakeTransport::Message &message, const char *topic)
|
|
{
|
|
TEST_ASSERT_EQUAL_STRING(topic, message.topic.c_str());
|
|
JsonDocument answer;
|
|
TEST_ASSERT_TRUE(deserializeJson(answer, message.json) == DeserializationError::Ok);
|
|
TEST_ASSERT_EQUAL_STRING("Alexa", answer["event"]["header"]["namespace"] | "");
|
|
TEST_ASSERT_EQUAL_STRING("ErrorResponse", answer["event"]["header"]["name"] | "");
|
|
TEST_ASSERT_EQUAL_STRING("token-of-the-directive", answer["event"]["header"]["correlationToken"] | "");
|
|
TEST_ASSERT_EQUAL_STRING("INVALID_DIRECTIVE", answer["event"]["payload"]["type"] | "");
|
|
TEST_ASSERT_TRUE(strlen(answer["event"]["payload"]["message"] | "") > 0);
|
|
TEST_ASSERT_TRUE(answer["context"].isNull()); // alexa-errorresponse.html: an ErrorResponse has no context
|
|
}
|
|
|
|
void setUp(void)
|
|
{
|
|
AlexaLog::setOutput(nullptr);
|
|
AlexaLog::setLevel(static_cast<AlexaLogLevel>(2));
|
|
seen = Seen();
|
|
handlerAnswers = true;
|
|
eventCalls = 0;
|
|
reportStateCalls = 0;
|
|
eventType = AlexaInterfaceType::UNKNOWN;
|
|
}
|
|
|
|
void tearDown(void) {}
|
|
|
|
void test_devices_are_found_by_their_endpoint_id_in_the_order_they_were_created(void)
|
|
{
|
|
AlexaDeviceList devices;
|
|
TEST_ASSERT_NULL(devices.first());
|
|
AlexaDevice *lamp = devices.add("Lamp", "root", "ESP-01", nullptr);
|
|
AlexaDevice *fan = devices.add("Fan", "root", "ESP-02", nullptr);
|
|
AlexaDevice *blind = devices.add("Blind", "root", "ESP-03", nullptr);
|
|
|
|
TEST_ASSERT_EQUAL(3, devices.size());
|
|
TEST_ASSERT_EQUAL_PTR(lamp, devices.first());
|
|
TEST_ASSERT_EQUAL_PTR(fan, lamp->nextDevice());
|
|
TEST_ASSERT_EQUAL_PTR(blind, fan->nextDevice());
|
|
TEST_ASSERT_NULL(blind->nextDevice());
|
|
TEST_ASSERT_EQUAL_PTR(fan, devices.find("ESP-02", 6));
|
|
// The id is a part of a topic: "ESP-02/alexaDirective"
|
|
TEST_ASSERT_EQUAL_PTR(fan, devices.find("ESP-02/alexaDirective", 6));
|
|
// An endpoint of another board
|
|
TEST_ASSERT_NULL(devices.find("ESP-04", 6));
|
|
TEST_ASSERT_NULL(devices.find("ESP-0", 5));
|
|
}
|
|
|
|
void test_one_handler_serves_two_devices_and_is_told_which(void)
|
|
{
|
|
FakeTransport transport;
|
|
AlexaDeviceList devices;
|
|
AlexaDevice *lamp = devices.add("Lamp", "root", "ESP-01", &transport);
|
|
AlexaDevice *fan = devices.add("Fan", "root", "ESP-02", &transport);
|
|
lamp->addCapability(AlexaInterfaces::PowerController);
|
|
AlexaCapability *fanPower = fan->addCapability(AlexaInterfaces::PowerController);
|
|
lamp->onDirective(onDirective);
|
|
fan->onDirective(onDirective);
|
|
|
|
JsonDocument message;
|
|
directiveFor(message, "ESP-02", "Alexa.PowerController", "TurnOn");
|
|
devices.find("ESP-02", 6)->handleDirective(message);
|
|
|
|
TEST_ASSERT_EQUAL(1, seen.calls);
|
|
TEST_ASSERT_EQUAL_PTR(fan, seen.device);
|
|
TEST_ASSERT_EQUAL_PTR(fanPower, seen.capability);
|
|
TEST_ASSERT_TRUE(AlexaInterfaceType::POWER_CONTROLLER == seen.type);
|
|
TEST_ASSERT_EQUAL_STRING("Alexa.PowerController", seen.ns.c_str());
|
|
TEST_ASSERT_EQUAL_STRING("TurnOn", seen.name.c_str());
|
|
TEST_ASSERT_EQUAL_STRING("", seen.instance.c_str());
|
|
TEST_ASSERT_EQUAL_STRING("token-of-the-directive", seen.correlationToken.c_str());
|
|
TEST_ASSERT_FALSE(seen.reportState);
|
|
|
|
TEST_ASSERT_EQUAL(1, transport.sent.size());
|
|
TEST_ASSERT_EQUAL_STRING("root/ESP-02/alexaResponce", transport.sent[0].topic.c_str());
|
|
JsonDocument answer;
|
|
TEST_ASSERT_TRUE(deserializeJson(answer, transport.sent[0].json) == DeserializationError::Ok);
|
|
TEST_ASSERT_EQUAL_STRING("Response", answer["event"]["header"]["name"] | "");
|
|
TEST_ASSERT_EQUAL_STRING("token-of-the-directive", answer["event"]["header"]["correlationToken"] | "");
|
|
TEST_ASSERT_EQUAL_STRING("ESP-02", answer["event"]["endpoint"]["endpointId"] | "");
|
|
TEST_ASSERT_EQUAL_STRING("ON", answer["context"]["properties"][0]["value"] | "");
|
|
TEST_ASSERT_EQUAL_STRING("2026-09-28T13:05:09Z", answer["context"]["properties"][0]["timeOfSample"] | "");
|
|
}
|
|
|
|
void test_directive_with_an_instance_reaches_the_capability_with_that_instance(void)
|
|
{
|
|
FakeTransport transport;
|
|
AlexaDevice blind("Blind", "root", "ESP-01", &transport);
|
|
AlexaCapability *lift = blind.addCapability(AlexaInterfaces::RangeController, "Blind.Lift");
|
|
AlexaCapability *tilt = blind.addCapability(AlexaInterfaces::RangeController, "Blind.Tilt");
|
|
blind.onDirective(onDirective);
|
|
|
|
JsonDocument message;
|
|
directiveFor(message, "ESP-01", "Alexa.RangeController", "SetRangeValue", "Blind.Tilt");
|
|
message["payload"]["rangeValue"] = 40;
|
|
blind.handleDirective(message);
|
|
|
|
TEST_ASSERT_EQUAL_PTR(tilt, seen.capability);
|
|
TEST_ASSERT_TRUE(AlexaInterfaceType::RANGE_CONTROLLER == seen.type);
|
|
TEST_ASSERT_EQUAL_STRING("Blind.Tilt", seen.instance.c_str());
|
|
TEST_ASSERT_EQUAL(40, seen.rangeValue);
|
|
|
|
TEST_ASSERT_EQUAL_PTR(lift, blind.findCapability("Alexa.RangeController", "Blind.Lift"));
|
|
TEST_ASSERT_NULL(blind.findCapability("Alexa.RangeController", "Blind.Slat"));
|
|
TEST_ASSERT_NULL(blind.findCapability("Alexa.RangeController", ""));
|
|
TEST_ASSERT_NULL(blind.findCapability("Alexa.ModeController", "Blind.Lift"));
|
|
}
|
|
|
|
void test_report_state_is_handed_over_without_a_capability_and_answered_with_a_state_report(void)
|
|
{
|
|
FakeTransport transport;
|
|
AlexaDevice lamp("Lamp", "root", "ESP-01", &transport);
|
|
lamp.addCapability(AlexaInterfaces::PowerController);
|
|
lamp.onDirective(onDirective);
|
|
|
|
JsonDocument message;
|
|
directiveFor(message, "ESP-01", "Alexa", "ReportState");
|
|
lamp.handleDirective(message);
|
|
|
|
TEST_ASSERT_TRUE(seen.reportState);
|
|
TEST_ASSERT_NULL(seen.capability);
|
|
TEST_ASSERT_EQUAL(1, transport.sent.size());
|
|
JsonDocument answer;
|
|
TEST_ASSERT_TRUE(deserializeJson(answer, transport.sent[0].json) == DeserializationError::Ok);
|
|
TEST_ASSERT_EQUAL_STRING("StateReport", answer["event"]["header"]["name"] | "");
|
|
TEST_ASSERT_EQUAL_STRING("OFF", answer["context"]["properties"][0]["value"] | "");
|
|
}
|
|
|
|
void test_one_capability_more_than_a_device_holds_is_refused_with_an_error(void)
|
|
{
|
|
CapturedLog log;
|
|
AlexaDevice device("Panel", "root", "ESP-01");
|
|
AlexaCapability *first = nullptr;
|
|
for (int i = 0; i < ALEX2ESP_MAX_CAPABILITIES; i++)
|
|
{
|
|
char instance[16];
|
|
snprintf(instance, sizeof(instance), "Panel.%d", i);
|
|
AlexaCapability *added = device.addCapability(AlexaInterfaces::ToggleController, instance);
|
|
TEST_ASSERT_NOT_NULL(added);
|
|
added->addFriendlyName("Switch", "en-US");
|
|
if (i == 0)
|
|
{
|
|
first = added;
|
|
}
|
|
}
|
|
std::string full;
|
|
serializeJson(device.getDeviceJSON(), full);
|
|
|
|
AlexaLog::setOutput(&log);
|
|
TEST_ASSERT_NULL(device.addCapability(AlexaInterfaces::PowerController));
|
|
TEST_ASSERT_NULL(device.addCapability(AlexaInterfaceType::BRIGHTNESS_CONTROLLER));
|
|
|
|
char expected[160];
|
|
snprintf(expected, sizeof(expected),
|
|
"error: ESP-01: capability Alexa.PowerController not added: the device has %d already "
|
|
"(ALEX2ESP_MAX_CAPABILITIES)",
|
|
ALEX2ESP_MAX_CAPABILITIES);
|
|
assertLogged(log, expected);
|
|
assertLogged(log, "error: ESP-01: capability Alexa.BrightnessController not added");
|
|
|
|
// What it has is still found and announced
|
|
TEST_ASSERT_EQUAL_PTR(first, device.addCapability(AlexaInterfaces::ToggleController, "Panel.0"));
|
|
TEST_ASSERT_NULL(device.findCapability("Alexa.PowerController", ""));
|
|
std::string announced;
|
|
serializeJson(device.getDeviceJSON(), announced);
|
|
TEST_ASSERT_EQUAL_STRING(full.c_str(), announced.c_str());
|
|
}
|
|
|
|
void test_directive_for_an_interface_the_device_lacks_is_answered_with_invalid_directive(void)
|
|
{
|
|
CapturedLog log;
|
|
AlexaLog::setOutput(&log);
|
|
FakeTransport transport;
|
|
AlexaDevice lamp("Lamp", "root", "ESP-01", &transport);
|
|
lamp.addCapability(AlexaInterfaces::PowerController);
|
|
lamp.onDirective(onDirective);
|
|
handlerAnswers = false; // as a handler that knows its own interfaces and leaves the others alone
|
|
|
|
JsonDocument message;
|
|
directiveFor(message, "ESP-01", "Alexa.LockController", "Lock");
|
|
lamp.handleDirective(message);
|
|
|
|
// The handler is asked first: it sees that the device has no such capability
|
|
TEST_ASSERT_EQUAL(1, seen.calls);
|
|
TEST_ASSERT_NULL(seen.capability);
|
|
TEST_ASSERT_TRUE(AlexaInterfaceType::UNKNOWN == seen.type);
|
|
TEST_ASSERT_EQUAL(1, transport.sent.size());
|
|
assertErrorResponse(transport.sent[0], "root/ESP-01/alexaResponce");
|
|
assertLogged(log, "error: ESP-01: Alexa.LockController.Lock refused as INVALID_DIRECTIVE: "
|
|
"the device has no such capability");
|
|
}
|
|
|
|
void test_instance_the_device_lacks_is_answered_with_invalid_directive(void)
|
|
{
|
|
FakeTransport transport;
|
|
AlexaDevice blind("Blind", "root", "ESP-01", &transport);
|
|
blind.addCapability(AlexaInterfaces::RangeController, "Blind.Lift");
|
|
blind.onDirective(onDirective);
|
|
handlerAnswers = false;
|
|
|
|
JsonDocument message;
|
|
directiveFor(message, "ESP-01", "Alexa.RangeController", "SetRangeValue", "Blind.Tilt");
|
|
blind.handleDirective(message);
|
|
|
|
TEST_ASSERT_NULL(seen.capability);
|
|
TEST_ASSERT_EQUAL(1, transport.sent.size());
|
|
assertErrorResponse(transport.sent[0], "root/ESP-01/alexaResponce");
|
|
}
|
|
|
|
void test_answer_of_the_handler_to_an_interface_the_device_lacks_is_the_only_answer(void)
|
|
{
|
|
FakeTransport transport;
|
|
AlexaDevice lamp("Lamp", "root", "ESP-01", &transport);
|
|
lamp.addCapability(AlexaInterfaces::PowerController);
|
|
lamp.onDirective(onDirective);
|
|
|
|
JsonDocument message;
|
|
directiveFor(message, "ESP-01", "Alexa.LockController", "Lock");
|
|
lamp.handleDirective(message);
|
|
|
|
TEST_ASSERT_EQUAL(1, transport.sent.size());
|
|
JsonDocument answer;
|
|
TEST_ASSERT_TRUE(deserializeJson(answer, transport.sent[0].json) == DeserializationError::Ok);
|
|
TEST_ASSERT_EQUAL_STRING("Response", answer["event"]["header"]["name"] | "");
|
|
}
|
|
|
|
void test_device_without_a_handler_answers_with_invalid_directive(void)
|
|
{
|
|
CapturedLog log;
|
|
AlexaLog::setOutput(&log);
|
|
FakeTransport transport;
|
|
AlexaDevice lamp("Lamp", "root", "ESP-01", &transport);
|
|
lamp.addCapability(AlexaInterfaces::PowerController);
|
|
|
|
JsonDocument message;
|
|
directiveFor(message, "ESP-01", "Alexa.PowerController", "TurnOn");
|
|
lamp.handleDirective(message);
|
|
directiveFor(message, "ESP-01", "Alexa", "ReportState");
|
|
lamp.handleDirective(message);
|
|
|
|
TEST_ASSERT_EQUAL(2, transport.sent.size());
|
|
assertErrorResponse(transport.sent[0], "root/ESP-01/alexaResponce");
|
|
assertErrorResponse(transport.sent[1], "root/ESP-01/alexaResponce");
|
|
assertLogged(log, "error: ESP-01: Alexa.PowerController.TurnOn refused as INVALID_DIRECTIVE: "
|
|
"the device has no handler, give it one with onDirective()");
|
|
}
|
|
|
|
void test_handler_that_sends_nothing_for_a_capability_of_the_device_is_reported(void)
|
|
{
|
|
CapturedLog log;
|
|
AlexaLog::setOutput(&log);
|
|
FakeTransport transport;
|
|
AlexaDevice lamp("Lamp", "root", "ESP-01", &transport);
|
|
lamp.addCapability(AlexaInterfaces::PowerController);
|
|
lamp.onDirective(onDirective);
|
|
handlerAnswers = false;
|
|
|
|
JsonDocument message;
|
|
directiveFor(message, "ESP-01", "Alexa.PowerController", "TurnOn");
|
|
lamp.handleDirective(message);
|
|
|
|
TEST_ASSERT_EQUAL(1, seen.calls);
|
|
TEST_ASSERT_EQUAL(0, transport.sent.size());
|
|
assertLogged(log, "error: ESP-01: the handler sent no answer to Alexa.PowerController.TurnOn");
|
|
}
|
|
|
|
void test_handler_of_1x_that_answers_later_is_not_reported(void)
|
|
{
|
|
CapturedLog log;
|
|
AlexaLog::setOutput(&log);
|
|
AlexaLog::setLevel(static_cast<AlexaLogLevel>(1));
|
|
FakeTransport transport;
|
|
AlexaDevice lamp("Lamp", "root", "ESP-01", &transport);
|
|
lamp.addCapability(AlexaInterfaceType::POWER_CONTROLLER);
|
|
lamp.registerEvent("Event", onEvent);
|
|
|
|
JsonDocument message;
|
|
directiveFor(message, "ESP-01", "Alexa.PowerController", "TurnOn");
|
|
lamp.handleDirective(message);
|
|
|
|
TEST_ASSERT_EQUAL(1, eventCalls);
|
|
TEST_ASSERT_EQUAL(0, transport.sent.size());
|
|
TEST_ASSERT_EQUAL_STRING("", log.text.c_str());
|
|
|
|
// The answer from loop(), with the token the sketch kept
|
|
TEST_ASSERT_TRUE(lamp.buildStatusMessage("token-of-the-directive", true).AddPowerControllerProp(PowerController::ON).send());
|
|
TEST_ASSERT_EQUAL(1, transport.sent.size());
|
|
}
|
|
|
|
void test_second_handler_for_an_event_replaces_the_first(void)
|
|
{
|
|
FakeTransport transport;
|
|
AlexaDevice lamp("Lamp", "root", "ESP-01", &transport);
|
|
lamp.registerEvent("Event", onReportState);
|
|
lamp.registerEvent("Event", onEvent);
|
|
|
|
JsonDocument message;
|
|
lamp.triggerEvent("Event", message, AlexaInterfaceType::UNKNOWN);
|
|
TEST_ASSERT_EQUAL(1, eventCalls);
|
|
TEST_ASSERT_EQUAL(0, reportStateCalls);
|
|
}
|
|
|
|
void test_one_handler_more_than_a_device_keeps_is_refused_with_an_error(void)
|
|
{
|
|
CapturedLog log;
|
|
FakeTransport transport;
|
|
AlexaDevice lamp("Lamp", "root", "ESP-01", &transport);
|
|
static const char *const NAMES[MAX_EVENTS] = {"Event", "ReportState", "DirectiveReceived", "A", "B",
|
|
"C", "D", "E", "F", "G"};
|
|
for (const char *name : NAMES)
|
|
{
|
|
lamp.registerEvent(name, onEvent);
|
|
}
|
|
|
|
AlexaLog::setOutput(&log);
|
|
lamp.registerEvent("Eleventh", onEvent);
|
|
lamp.registerEvent(nullptr, onEvent);
|
|
lamp.registerEvent("Event", nullptr);
|
|
|
|
assertLogged(log, "error: ESP-01: handler for Eleventh not registered: the device has 10 already (MAX_EVENTS)");
|
|
assertLogged(log, "error: ESP-01: handler not registered: registerEvent() takes the name of the event and a function");
|
|
JsonDocument message;
|
|
TEST_ASSERT_FALSE(lamp.triggerEvent("Eleventh", message, AlexaInterfaceType::UNKNOWN, false));
|
|
TEST_ASSERT_TRUE(lamp.triggerEvent("Event", message, AlexaInterfaceType::UNKNOWN, false));
|
|
TEST_ASSERT_TRUE(lamp.triggerEvent("G", message, AlexaInterfaceType::UNKNOWN, false));
|
|
TEST_ASSERT_EQUAL(2, eventCalls);
|
|
}
|
|
|
|
void test_level_above_the_ceiling_of_the_build_is_reported(void)
|
|
{
|
|
CapturedLog log;
|
|
AlexaLog::setOutput(&log);
|
|
|
|
AlexaLog::setLevel(static_cast<AlexaLogLevel>(ALEX2ESP_LOG_MAX));
|
|
TEST_ASSERT_EQUAL_STRING("", log.text.c_str());
|
|
|
|
AlexaLog::setLevel(static_cast<AlexaLogLevel>(ALEX2ESP_LOG_MAX + 1));
|
|
char expected[160];
|
|
snprintf(expected, sizeof(expected),
|
|
"error: log level %d asked for, the lines of this build end at level %d: build with -DALEX2ESP_LOG_MAX=%d",
|
|
ALEX2ESP_LOG_MAX + 1, ALEX2ESP_LOG_MAX, ALEX2ESP_LOG_MAX + 1);
|
|
assertLogged(log, expected);
|
|
}
|
|
|
|
void test_level_none_prints_nothing(void)
|
|
{
|
|
CapturedLog log;
|
|
AlexaLog::setOutput(&log);
|
|
AlexaLog::setLevel(static_cast<AlexaLogLevel>(0));
|
|
FakeTransport transport;
|
|
AlexaDevice lamp("Lamp", "root", "ESP-01", &transport);
|
|
|
|
JsonDocument message;
|
|
directiveFor(message, "ESP-01", "Alexa.PowerController", "TurnOn");
|
|
lamp.handleDirective(message);
|
|
|
|
// The answer is sent all the same
|
|
assertErrorResponse(transport.sent[0], "root/ESP-01/alexaResponce");
|
|
TEST_ASSERT_EQUAL_STRING("", log.text.c_str());
|
|
}
|
|
|
|
void test_handlers_of_1x_get_report_state_and_the_type_of_every_other_directive(void)
|
|
{
|
|
FakeTransport transport;
|
|
AlexaDevice blind("Blind", "root", "ESP-01", &transport);
|
|
blind.addCapability(AlexaInterfaceType::POWER_CONTROLLER);
|
|
blind.addCapability(AlexaInterfaceType::TOGGLE_CONTROLLER)->setInstance("ESP-01.Toggle");
|
|
blind.registerEvent("ReportState", onReportState);
|
|
blind.registerEvent("Event", onEvent);
|
|
|
|
JsonDocument message;
|
|
directiveFor(message, "ESP-01", "Alexa.ToggleController", "TurnOn", "ESP-01.Toggle");
|
|
blind.handleDirective(message);
|
|
TEST_ASSERT_EQUAL(1, eventCalls);
|
|
TEST_ASSERT_EQUAL(0, reportStateCalls);
|
|
TEST_ASSERT_TRUE(AlexaInterfaceType::TOGGLE_CONTROLLER == eventType);
|
|
|
|
directiveFor(message, "ESP-01", "Alexa", "ReportState");
|
|
blind.handleDirective(message);
|
|
TEST_ASSERT_EQUAL(1, eventCalls);
|
|
TEST_ASSERT_EQUAL(1, reportStateCalls);
|
|
|
|
// With a handler of 2.0 the device calls that one
|
|
blind.onDirective(onDirective);
|
|
directiveFor(message, "ESP-01", "Alexa.PowerController", "TurnOff");
|
|
blind.handleDirective(message);
|
|
TEST_ASSERT_EQUAL(1, eventCalls);
|
|
TEST_ASSERT_EQUAL(1, seen.calls);
|
|
}
|
|
|
|
int main(int, char **)
|
|
{
|
|
UNITY_BEGIN();
|
|
RUN_TEST(test_devices_are_found_by_their_endpoint_id_in_the_order_they_were_created);
|
|
RUN_TEST(test_one_handler_serves_two_devices_and_is_told_which);
|
|
RUN_TEST(test_directive_with_an_instance_reaches_the_capability_with_that_instance);
|
|
RUN_TEST(test_report_state_is_handed_over_without_a_capability_and_answered_with_a_state_report);
|
|
RUN_TEST(test_one_capability_more_than_a_device_holds_is_refused_with_an_error);
|
|
RUN_TEST(test_directive_for_an_interface_the_device_lacks_is_answered_with_invalid_directive);
|
|
RUN_TEST(test_instance_the_device_lacks_is_answered_with_invalid_directive);
|
|
RUN_TEST(test_answer_of_the_handler_to_an_interface_the_device_lacks_is_the_only_answer);
|
|
RUN_TEST(test_device_without_a_handler_answers_with_invalid_directive);
|
|
RUN_TEST(test_handler_that_sends_nothing_for_a_capability_of_the_device_is_reported);
|
|
RUN_TEST(test_handler_of_1x_that_answers_later_is_not_reported);
|
|
RUN_TEST(test_second_handler_for_an_event_replaces_the_first);
|
|
RUN_TEST(test_one_handler_more_than_a_device_keeps_is_refused_with_an_error);
|
|
RUN_TEST(test_level_above_the_ceiling_of_the_build_is_reported);
|
|
RUN_TEST(test_level_none_prints_nothing);
|
|
RUN_TEST(test_handlers_of_1x_get_report_state_and_the_type_of_every_other_directive);
|
|
return UNITY_END();
|
|
}
|