feat(api): Settings-Typ standalone und wifi.standalone in system/info

This commit is contained in:
Michael Sauer
2026-10-02 00:10:38 +02:00
parent 8469b92338
commit 416307083f
5 changed files with 273 additions and 0 deletions
+23
View File
@@ -1,5 +1,6 @@
#include "fake_api_ctx.h"
#include <stdio.h>
#include <string.h>
#include "light/nl_boot_state.h"
@@ -12,6 +13,10 @@ nl_ctrl_err_t fake_apply_result;
int fake_apply_calls, fake_save_calls, fake_save_result;
int fake_wifi_calls, fake_wifi_result;
char fake_wifi_ssid[NL_SSID_LEN], fake_wifi_pass[NL_PASS_LEN];
int fake_standalone_calls, fake_standalone_result;
bool fake_standalone_enabled, fake_standalone_pulse;
char fake_standalone_ssid[NL_SSID_LEN], fake_standalone_pass[NL_PASS_LEN];
uint8_t fake_standalone_channel;
int fake_factory_calls, fake_ping_calls, fake_fw_result;
char fake_fw_url[256];
bool fake_open;
@@ -49,6 +54,18 @@ static int wifi_configure(const char *ssid, const char *pass)
return fake_wifi_result;
}
static int fake_standalone_configure(bool enabled, const char *ssid, const char *pass,
uint8_t channel, bool pulse)
{
fake_standalone_calls++;
fake_standalone_enabled = enabled;
fake_standalone_pulse = pulse;
fake_standalone_channel = channel;
snprintf(fake_standalone_ssid, sizeof fake_standalone_ssid, "%s", ssid ? ssid : "");
snprintf(fake_standalone_pass, sizeof fake_standalone_pass, "%s", pass ? pass : "");
return fake_standalone_result;
}
static bool open_access(void) { return fake_open; }
static void get_info(nl_sys_info_t *out) { *out = fake_info; }
static void factory_reset(void) { fake_factory_calls++; }
@@ -83,6 +100,10 @@ void fake_api_reset(const char *board_id)
fake_apply_calls = fake_save_calls = fake_save_result = 0;
fake_wifi_calls = fake_wifi_result = 0;
fake_wifi_ssid[0] = fake_wifi_pass[0] = '\0';
fake_standalone_calls = fake_standalone_result = 0;
fake_standalone_enabled = fake_standalone_pulse = false;
fake_standalone_ssid[0] = fake_standalone_pass[0] = '\0';
fake_standalone_channel = 0;
fake_factory_calls = fake_ping_calls = 0;
fake_fw_result = -1;
fake_fw_url[0] = '\0';
@@ -96,6 +117,8 @@ void fake_api_reset(const char *board_id)
fake_ctx.get_settings = get_settings;
fake_ctx.save_settings = save_settings;
fake_ctx.wifi_configure = wifi_configure;
fake_ctx.standalone_configure = fake_standalone_configure;
fake_ctx.hostname = "ninlight-681f";
fake_ctx.open_access = open_access;
fake_ctx.get_info = get_info;
fake_ctx.factory_reset = factory_reset;
+4
View File
@@ -12,6 +12,10 @@ extern nl_ctrl_err_t fake_apply_result;
extern int fake_apply_calls, fake_save_calls, fake_save_result;
extern int fake_wifi_calls, fake_wifi_result;
extern char fake_wifi_ssid[NL_SSID_LEN], fake_wifi_pass[NL_PASS_LEN];
extern int fake_standalone_calls, fake_standalone_result;
extern bool fake_standalone_enabled, fake_standalone_pulse;
extern char fake_standalone_ssid[NL_SSID_LEN], fake_standalone_pass[NL_PASS_LEN];
extern uint8_t fake_standalone_channel;
extern int fake_factory_calls, fake_ping_calls, fake_fw_result;
extern char fake_fw_url[256];
extern bool fake_open;
+174
View File
@@ -589,6 +589,169 @@ static void test_put_sacn_layout_and_start_slot_together(void)
TEST_ASSERT_EQUAL_UINT16(510, fake_settings.sacn_start_slot);
}
/* --- standalone --- */
static void test_standalone_requires_a_password(void)
{
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"standalone\",\"enabled\":true}");
TEST_ASSERT_EQUAL_INT(400, resp.status);
TEST_ASSERT_EQUAL_STRING("password required", error_text());
TEST_ASSERT_EQUAL_INT(0, fake_standalone_calls);
TEST_ASSERT_EQUAL_INT(0, fake_save_calls);
}
/* Ein gespeichertes Passwort genuegt - man soll den Kanal aendern koennen, ohne das
* Passwort erneut zu schicken. */
static void test_standalone_keeps_a_stored_password(void)
{
strcpy(fake_settings.ap_pass, "geheim123");
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN,
"{\"type\":\"standalone\",\"enabled\":true,\"channel\":11}");
TEST_ASSERT_EQUAL_INT(202, resp.status);
TEST_ASSERT_EQUAL_INT(11, fake_standalone_channel);
TEST_ASSERT_EQUAL_STRING("geheim123", fake_standalone_pass);
}
static void test_standalone_rejects_a_short_password(void)
{
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN,
"{\"type\":\"standalone\",\"enabled\":true,\"password\":\"1234567\"}");
TEST_ASSERT_EQUAL_INT(400, resp.status);
TEST_ASSERT_EQUAL_STRING("invalid password", error_text());
/* Die Fehlermeldung nennt das Feld, nie den Wert. */
TEST_ASSERT_NULL(strstr(resp.body, "1234567"));
TEST_ASSERT_EQUAL_INT(0, fake_standalone_calls);
}
static void test_standalone_rejects_a_channel_outside_the_band(void)
{
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN,
"{\"type\":\"standalone\",\"enabled\":true,\"password\":\"geheim123\",\"channel\":14}");
TEST_ASSERT_EQUAL_INT(400, resp.status);
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN,
"{\"type\":\"standalone\",\"enabled\":true,\"password\":\"geheim123\",\"channel\":0}");
TEST_ASSERT_EQUAL_INT(400, resp.status);
TEST_ASSERT_EQUAL_INT(0, fake_standalone_calls);
}
static void test_standalone_accepts_a_full_request(void)
{
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN,
"{\"type\":\"standalone\",\"enabled\":true,\"password\":\"geheim123\","
"\"ssid\":\"Wohnzimmerlampe\",\"channel\":6,\"pulse\":false}");
TEST_ASSERT_EQUAL_INT(202, resp.status);
TEST_ASSERT_EQUAL_STRING("standalone", cJSON_GetStringValue(field("status")));
TEST_ASSERT_EQUAL_INT(1, fake_standalone_calls);
TEST_ASSERT_TRUE(fake_standalone_enabled);
TEST_ASSERT_EQUAL_STRING("Wohnzimmerlampe", fake_standalone_ssid);
TEST_ASSERT_EQUAL_STRING("geheim123", fake_standalone_pass);
TEST_ASSERT_EQUAL_INT(6, fake_standalone_channel);
TEST_ASSERT_FALSE(fake_standalone_pulse);
/* Gespeichert wird auch: sonst ist der Modus nach dem Neustart weg. */
TEST_ASSERT_EQUAL_INT(1, fake_save_calls);
TEST_ASSERT_EQUAL_UINT8(1, fake_settings.standalone);
TEST_ASSERT_EQUAL_STRING("geheim123", fake_settings.ap_pass);
}
/* Abschalten braucht kein Passwort. */
static void test_standalone_can_be_switched_off(void)
{
fake_settings.standalone = 1;
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"standalone\",\"enabled\":false}");
TEST_ASSERT_EQUAL_INT(202, resp.status);
TEST_ASSERT_EQUAL_INT(1, fake_standalone_calls);
TEST_ASSERT_FALSE(fake_standalone_enabled);
TEST_ASSERT_EQUAL_UINT8(0, fake_settings.standalone);
}
static void test_standalone_reports_a_busy_radio_and_a_failed_save(void)
{
strcpy(fake_settings.ap_pass, "geheim123");
fake_standalone_result = -1;
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"standalone\",\"enabled\":true}");
TEST_ASSERT_EQUAL_INT(503, resp.status);
TEST_ASSERT_EQUAL_STRING("wifi busy", error_text());
fake_standalone_result = 0;
fake_standalone_calls = 0;
fake_save_result = -1;
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"standalone\",\"enabled\":true}");
TEST_ASSERT_EQUAL_INT(500, resp.status);
/* Ohne gespeicherten Datensatz darf der Wechsel nicht angestossen werden. */
TEST_ASSERT_EQUAL_INT(0, fake_standalone_calls);
}
static void test_standalone_rejects_bad_types_of_the_other_fields(void)
{
strcpy(fake_settings.ap_pass, "geheim123");
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"standalone\",\"enabled\":\"yes\"}");
TEST_ASSERT_EQUAL_STRING("invalid enabled", error_text());
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"standalone\",\"enabled\":true,\"pulse\":1}");
TEST_ASSERT_EQUAL_STRING("invalid pulse", error_text());
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"standalone\",\"enabled\":true,\"ssid\":\"\"}");
TEST_ASSERT_EQUAL_STRING("invalid ssid", error_text());
TEST_ASSERT_EQUAL_INT(0, fake_standalone_calls);
}
/* Das AP-Passwort darf nirgends in der Antwort auftauchen. */
static void test_settings_show_standalone_without_the_password(void)
{
cJSON *o, *sa;
fake_settings.standalone = 1;
fake_settings.ap_channel = 11;
fake_settings.standalone_pulse = 0;
strcpy(fake_settings.ap_ssid, "Wohnzimmerlampe");
strcpy(fake_settings.ap_pass, "geheim123");
call(NL_HTTP_GET, "/api/v1/system/settings", TOKEN, NULL);
TEST_ASSERT_EQUAL_INT(200, resp.status);
TEST_ASSERT_NULL(strstr(resp.body, "geheim123"));
o = cJSON_Parse(resp.body);
sa = cJSON_GetObjectItemCaseSensitive(o, "standalone");
TEST_ASSERT_TRUE(cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(sa, "enabled")));
TEST_ASSERT_EQUAL_STRING("Wohnzimmerlampe",
cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(sa, "ssid")));
TEST_ASSERT_EQUAL_INT(11, (int)cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(sa, "channel")));
TEST_ASSERT_FALSE(cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(sa, "pulse")));
cJSON_Delete(o);
}
/* Ohne eigenen Namen zeigt die Antwort den WIRKSAMEN, nicht einen leeren String -
* sonst muesste jeder Aufrufer die Vorgabe selbst nachbilden. */
static void test_settings_show_the_effective_ap_name(void)
{
cJSON *o, *sa;
fake_settings.ap_ssid[0] = '\0';
call(NL_HTTP_GET, "/api/v1/system/settings", TOKEN, NULL);
o = cJSON_Parse(resp.body);
sa = cJSON_GetObjectItemCaseSensitive(o, "standalone");
TEST_ASSERT_EQUAL_STRING("ninlight-681f",
cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(sa, "ssid")));
/* Kanal 0 im Datensatz heisst 6. */
TEST_ASSERT_EQUAL_INT(6, (int)cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(sa, "channel")));
cJSON_Delete(o);
}
/* "ap" allein reicht nicht: denselben Wert meldet der Onboarding-AP. Ohne dieses Feld
* boete die App einer fertig eingerichteten Lampe ein Onboarding an. */
static void test_info_tells_standalone_from_onboarding(void)
{
cJSON *o, *w;
fake_info.wifi_mode = "ap";
fake_info.wifi_standalone = true;
call(NL_HTTP_GET, "/api/v1/system/info", NULL, NULL);
o = cJSON_Parse(resp.body);
w = cJSON_GetObjectItemCaseSensitive(o, "wifi");
TEST_ASSERT_TRUE(cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(w, "standalone")));
cJSON_Delete(o);
fake_info.wifi_standalone = false;
call(NL_HTTP_GET, "/api/v1/system/info", NULL, NULL);
o = cJSON_Parse(resp.body);
w = cJSON_GetObjectItemCaseSensitive(o, "wifi");
TEST_ASSERT_TRUE(cJSON_IsFalse(cJSON_GetObjectItemCaseSensitive(w, "standalone")));
cJSON_Delete(o);
}
int main(void)
{
UNITY_BEGIN();
@@ -627,5 +790,16 @@ int main(void)
RUN_TEST(test_info_exposes_sacn_state);
RUN_TEST(test_info_reports_a_dead_sacn_task);
RUN_TEST(test_commands);
RUN_TEST(test_standalone_requires_a_password);
RUN_TEST(test_standalone_keeps_a_stored_password);
RUN_TEST(test_standalone_rejects_a_short_password);
RUN_TEST(test_standalone_rejects_a_channel_outside_the_band);
RUN_TEST(test_standalone_accepts_a_full_request);
RUN_TEST(test_standalone_can_be_switched_off);
RUN_TEST(test_standalone_reports_a_busy_radio_and_a_failed_save);
RUN_TEST(test_standalone_rejects_bad_types_of_the_other_fields);
RUN_TEST(test_settings_show_standalone_without_the_password);
RUN_TEST(test_settings_show_the_effective_ap_name);
RUN_TEST(test_info_tells_standalone_from_onboarding);
return UNITY_END();
}