Add firmware slices 1–2: skeleton and Wi-Fi provisioning
Implement ESP-IDF walking skeleton with SoftAP, health API, and host tests, then Slice 2 ISecureStore/NvsSecureStore, STA join, POST /api/wifi, and the provisioning web UI. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
# Host unit tests for components/core (plain CMake + ctest)
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(digiradio_core_tests LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS ON)
|
||||
|
||||
enable_testing()
|
||||
|
||||
set(CORE_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../src")
|
||||
|
||||
add_library(digiradio_core STATIC
|
||||
"${CORE_SRC_DIR}/FirmwareVersion.cpp"
|
||||
"${CORE_SRC_DIR}/HealthStatus.cpp"
|
||||
"${CORE_SRC_DIR}/HealthStatusJson.cpp"
|
||||
"${CORE_SRC_DIR}/Secret.cpp"
|
||||
"${CORE_SRC_DIR}/WifiSsid.cpp"
|
||||
"${CORE_SRC_DIR}/WifiCredentials.cpp"
|
||||
"${CORE_SRC_DIR}/WifiProvisionJson.cpp"
|
||||
)
|
||||
target_include_directories(digiradio_core PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../include"
|
||||
)
|
||||
|
||||
add_executable(health_status_test health_status_test.cpp)
|
||||
target_link_libraries(health_status_test PRIVATE digiradio_core)
|
||||
add_test(NAME health_status_test COMMAND health_status_test)
|
||||
|
||||
add_executable(wifi_provision_test wifi_provision_test.cpp)
|
||||
target_link_libraries(wifi_provision_test PRIVATE digiradio_core)
|
||||
add_test(NAME wifi_provision_test COMMAND wifi_provision_test)
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* @file health_status_test.cpp
|
||||
* @brief Host tests for HealthStatus JSON serialisation.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
|
||||
#include "core/FirmwareVersion.hpp"
|
||||
#include "core/HealthStatus.hpp"
|
||||
#include "core/HealthStatusJson.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief expectEqual — assert two strings match.
|
||||
*
|
||||
* @dname expectEqual
|
||||
* @param actual Observed value.
|
||||
* @param expected Expected value.
|
||||
* @return true when equal.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] bool expectEqual(const std::string& actual,
|
||||
const std::string& expected)
|
||||
{
|
||||
if (actual == expected) {
|
||||
return true;
|
||||
}
|
||||
std::cerr << "expected: " << expected << "\nactual: " << actual << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief runHealthStatusJsonTest — verify nominal JSON output.
|
||||
*
|
||||
* @dname runHealthStatusJsonTest
|
||||
* @param none
|
||||
* @return EXIT_SUCCESS or EXIT_FAILURE.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] int runHealthStatusJsonTest()
|
||||
{
|
||||
const core::HealthStatus status =
|
||||
core::HealthStatus::ok(core::FirmwareVersion("0.1.0"));
|
||||
const std::string json = core::serializeHealthStatusJson(status);
|
||||
if (!expectEqual(json, R"({"status":"ok","fw":"0.1.0"})")) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief main — host test entry point.
|
||||
*
|
||||
* @dname main
|
||||
* @param none
|
||||
* @return EXIT_SUCCESS when all tests pass.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
int main()
|
||||
{
|
||||
return runHealthStatusJsonTest();
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* @file wifi_provision_test.cpp
|
||||
* @brief Host tests for Wi-Fi provisioning JSON parse/serialise.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
|
||||
#include "core/ParseError.hpp"
|
||||
#include "core/WifiCredentials.hpp"
|
||||
#include "core/WifiProvisionJson.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief expectEqual — assert two strings match.
|
||||
*
|
||||
* @dname expectEqual
|
||||
* @param actual Observed value.
|
||||
* @param expected Expected value.
|
||||
* @return true when equal.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] bool expectEqual(const std::string& actual,
|
||||
const std::string& expected)
|
||||
{
|
||||
if (actual == expected) {
|
||||
return true;
|
||||
}
|
||||
std::cerr << "expected: " << expected << "\nactual: " << actual << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief runWifiProvisionParseTest — verify nominal JSON parsing.
|
||||
*
|
||||
* @dname runWifiProvisionParseTest
|
||||
* @return EXIT_SUCCESS or EXIT_FAILURE.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] int runWifiProvisionParseTest()
|
||||
{
|
||||
const auto parsed = core::parseWifiProvisionJson(
|
||||
R"({"ssid":"MyNet","password":"secret12"})");
|
||||
if (!parsed) {
|
||||
std::cerr << "parse failed\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (parsed->ssid().value() != "MyNet") {
|
||||
std::cerr << "unexpected ssid\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
bool pwdOk = false;
|
||||
parsed->password().usePlaintext(
|
||||
[&](std::string_view pwd) { pwdOk = (pwd == "secret12"); });
|
||||
if (!pwdOk) {
|
||||
std::cerr << "unexpected password\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief runWifiProvisionRejectTest — verify invalid SSID is rejected.
|
||||
*
|
||||
* @dname runWifiProvisionRejectTest
|
||||
* @return EXIT_SUCCESS or EXIT_FAILURE.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] int runWifiProvisionRejectTest()
|
||||
{
|
||||
const auto parsed =
|
||||
core::parseWifiProvisionJson(R"({"ssid":"","password":"secret12"})");
|
||||
if (parsed || parsed.error() != core::ParseError::InvalidSsid) {
|
||||
std::cerr << "expected InvalidSsid\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief runWifiProvisionSerialiseTest — verify saved response JSON.
|
||||
*
|
||||
* @dname runWifiProvisionSerialiseTest
|
||||
* @return EXIT_SUCCESS or EXIT_FAILURE.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] int runWifiProvisionSerialiseTest()
|
||||
{
|
||||
if (!expectEqual(core::serializeWifiProvisionSavedJson(3),
|
||||
R"({"status":"saved","reboot_in_sec":3})")) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief main — host test entry point.
|
||||
*
|
||||
* @dname main
|
||||
* @return EXIT_SUCCESS when all tests pass.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
int main()
|
||||
{
|
||||
if (runWifiProvisionParseTest() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (runWifiProvisionRejectTest() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (runWifiProvisionSerialiseTest() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user