fix(api): standalone rollt bei 503 zurueck, Tests fuer Grenzen und behaltene Felder

This commit is contained in:
Michael Sauer
2026-10-02 00:21:40 +02:00
parent 416307083f
commit e36add5030
2 changed files with 101 additions and 8 deletions
+7 -3
View File
@@ -324,7 +324,7 @@ static void handle_get_settings(const nl_api_ctx_t *ctx, nl_api_response_t *resp
/* Der WIRKSAME Name, nicht der gespeicherte: ohne eigenen Namen heisst der AP /* Der WIRKSAME Name, nicht der gespeicherte: ohne eigenen Namen heisst der AP
* wie der Hostname. Wer hier einen leeren String saehe, muesste die Vorgabe * wie der Hostname. Wer hier einen leeren String saehe, muesste die Vorgabe
* selbst nachbilden. Das AP-Passwort steht bewusst nicht hier. */ * selbst nachbilden. Das AP-Passwort steht bewusst nicht hier. */
cJSON_AddStringToObject(sa, "ssid", s.ap_ssid[0] ? s.ap_ssid : (ctx->hostname ? ctx->hostname : "")); cJSON_AddStringToObject(sa, "ssid", s.ap_ssid[0] ? s.ap_ssid : ctx->hostname);
cJSON_AddNumberToObject(sa, "channel", s.ap_channel ? s.ap_channel : 6); cJSON_AddNumberToObject(sa, "channel", s.ap_channel ? s.ap_channel : 6);
cJSON_AddBoolToObject(sa, "pulse", s.standalone_pulse != 0); cJSON_AddBoolToObject(sa, "pulse", s.standalone_pulse != 0);
} }
@@ -366,6 +366,7 @@ static void handle_put_settings(const nl_api_ctx_t *ctx, const nl_http_request_t
long ch = 0; long ch = 0;
int asp, app, chp; int asp, app, chp;
bool will_enable; bool will_enable;
nl_settings_t prev = s; /* Stand vor der Aenderung, zum Zurueckrollen bei 503 */
will_enable = cJSON_IsBool(en) && cJSON_IsTrue(en); will_enable = cJSON_IsBool(en) && cJSON_IsTrue(en);
asp = get_string(j, "ssid", 1, NL_SSID_LEN - 1, &as); asp = get_string(j, "ssid", 1, NL_SSID_LEN - 1, &as);
@@ -401,9 +402,12 @@ static void handle_put_settings(const nl_api_ctx_t *ctx, const nl_http_request_t
* Antwort ist 202 und muss vorher ausgefuellt sein. */ * Antwort ist 202 und muss vorher ausgefuellt sein. */
if (ctx->save_settings(&s) != 0) { if (ctx->save_settings(&s) != 0) {
reply_error(resp, 500, "save failed"); reply_error(resp, 500, "save failed");
} else if (!ctx->standalone_configure || } else if (ctx->standalone_configure(will_enable, s.ap_ssid, s.ap_pass,
ctx->standalone_configure(will_enable, s.ap_ssid, s.ap_pass,
s.ap_channel, s.standalone_pulse != 0) != 0) { s.ap_channel, s.standalone_pulse != 0) != 0) {
/* 503 heisst "nicht uebernommen": was schon im Flash steht, muss zurueck,
* sonst startet die Lampe nach einem Neustart doch im neuen Modus. Best
* effort - scheitert auch das, bleibt nur die Meldung. */
(void)ctx->save_settings(&prev);
reply_error(resp, 503, "wifi busy"); reply_error(resp, 503, "wifi busy");
} else { } else {
reply(resp, 202, "{\"status\":\"standalone\"}"); reply(resp, 202, "{\"status\":\"standalone\"}");
+94 -5
View File
@@ -641,6 +641,7 @@ static void test_standalone_accepts_a_full_request(void)
"\"ssid\":\"Wohnzimmerlampe\",\"channel\":6,\"pulse\":false}"); "\"ssid\":\"Wohnzimmerlampe\",\"channel\":6,\"pulse\":false}");
TEST_ASSERT_EQUAL_INT(202, resp.status); TEST_ASSERT_EQUAL_INT(202, resp.status);
TEST_ASSERT_EQUAL_STRING("standalone", cJSON_GetStringValue(field("status"))); TEST_ASSERT_EQUAL_STRING("standalone", cJSON_GetStringValue(field("status")));
TEST_ASSERT_NULL(strstr(resp.body, "geheim123"));
TEST_ASSERT_EQUAL_INT(1, fake_standalone_calls); TEST_ASSERT_EQUAL_INT(1, fake_standalone_calls);
TEST_ASSERT_TRUE(fake_standalone_enabled); TEST_ASSERT_TRUE(fake_standalone_enabled);
TEST_ASSERT_EQUAL_STRING("Wohnzimmerlampe", fake_standalone_ssid); TEST_ASSERT_EQUAL_STRING("Wohnzimmerlampe", fake_standalone_ssid);
@@ -664,23 +665,106 @@ static void test_standalone_can_be_switched_off(void)
TEST_ASSERT_EQUAL_UINT8(0, fake_settings.standalone); TEST_ASSERT_EQUAL_UINT8(0, fake_settings.standalone);
} }
static void test_standalone_reports_a_busy_radio_and_a_failed_save(void) static void test_standalone_busy_radio_answers_503_and_rolls_back(void)
{ {
strcpy(fake_settings.ap_pass, "geheim123"); strcpy(fake_settings.ap_pass, "geheim123");
fake_settings.standalone = 0;
fake_standalone_result = -1; fake_standalone_result = -1;
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"standalone\",\"enabled\":true}"); call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"standalone\",\"enabled\":true}");
TEST_ASSERT_EQUAL_INT(503, resp.status); TEST_ASSERT_EQUAL_INT(503, resp.status);
TEST_ASSERT_EQUAL_STRING("wifi busy", error_text()); TEST_ASSERT_EQUAL_STRING("wifi busy", error_text());
TEST_ASSERT_NULL(strstr(resp.body, "geheim123"));
/* "nicht uebernommen" gilt auch im Flash. */
TEST_ASSERT_EQUAL_UINT8(0, fake_settings.standalone);
TEST_ASSERT_EQUAL_INT(2, fake_save_calls);
}
fake_standalone_result = 0; static void test_standalone_rollback_restores_every_changed_field(void)
fake_standalone_calls = 0; {
strcpy(fake_settings.ap_pass, "altpass123");
strcpy(fake_settings.ap_ssid, "alt");
fake_settings.ap_channel = 3;
fake_settings.standalone_pulse = 1;
fake_standalone_result = -1;
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN,
"{\"type\":\"standalone\",\"enabled\":true,\"password\":\"neupass123\","
"\"ssid\":\"neu\",\"channel\":11,\"pulse\":false}");
TEST_ASSERT_EQUAL_INT(503, resp.status);
TEST_ASSERT_EQUAL_STRING("altpass123", fake_settings.ap_pass);
TEST_ASSERT_EQUAL_STRING("alt", fake_settings.ap_ssid);
TEST_ASSERT_EQUAL_UINT8(3, fake_settings.ap_channel);
TEST_ASSERT_EQUAL_UINT8(1, fake_settings.standalone_pulse);
}
static void test_standalone_failed_save_answers_500_without_switching(void)
{
strcpy(fake_settings.ap_pass, "geheim123");
fake_save_result = -1; fake_save_result = -1;
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"standalone\",\"enabled\":true}"); call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"standalone\",\"enabled\":true}");
TEST_ASSERT_EQUAL_INT(500, resp.status); TEST_ASSERT_EQUAL_INT(500, resp.status);
/* Ohne gespeicherten Datensatz darf der Wechsel nicht angestossen werden. */
TEST_ASSERT_EQUAL_INT(0, fake_standalone_calls); TEST_ASSERT_EQUAL_INT(0, fake_standalone_calls);
} }
/* Nicht angegebene Felder behalten ihren gespeicherten Wert. */
static void test_standalone_unspecified_fields_keep_their_stored_values(void)
{
strcpy(fake_settings.ap_pass, "geheim123");
strcpy(fake_settings.ap_ssid, "Gespeichert");
fake_settings.ap_channel = 11;
fake_settings.standalone_pulse = 0;
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"standalone\",\"enabled\":true}");
TEST_ASSERT_EQUAL_INT(202, resp.status);
TEST_ASSERT_NULL(strstr(resp.body, "geheim123"));
TEST_ASSERT_EQUAL_STRING("Gespeichert", fake_standalone_ssid);
TEST_ASSERT_EQUAL_UINT8(11, fake_standalone_channel);
TEST_ASSERT_FALSE(fake_standalone_pulse);
fake_settings.standalone_pulse = 1;
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"standalone\",\"enabled\":true}");
TEST_ASSERT_TRUE(fake_standalone_pulse);
}
static void test_standalone_length_limits(void)
{
char body[256], pw[80], ss[48];
memset(pw, 'p', 64); pw[64] = '\0';
snprintf(body, sizeof body, "{\"type\":\"standalone\",\"enabled\":true,\"password\":\"%s\"}", pw);
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, body);
TEST_ASSERT_EQUAL_INT(202, resp.status);
TEST_ASSERT_EQUAL_STRING(pw, fake_standalone_pass);
memset(pw, 'p', 65); pw[65] = '\0';
snprintf(body, sizeof body, "{\"type\":\"standalone\",\"enabled\":true,\"password\":\"%s\"}", pw);
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, body);
TEST_ASSERT_EQUAL_INT(400, resp.status);
TEST_ASSERT_NULL(strstr(resp.body, pw));
memset(ss, 's', 32); ss[32] = '\0';
snprintf(body, sizeof body,
"{\"type\":\"standalone\",\"enabled\":true,\"password\":\"geheim123\",\"ssid\":\"%s\"}", ss);
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, body);
TEST_ASSERT_EQUAL_INT(202, resp.status);
TEST_ASSERT_EQUAL_STRING(ss, fake_standalone_ssid);
memset(ss, 's', 33); ss[33] = '\0';
snprintf(body, sizeof body,
"{\"type\":\"standalone\",\"enabled\":true,\"password\":\"geheim123\",\"ssid\":\"%s\"}", ss);
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, body);
TEST_ASSERT_EQUAL_INT(400, resp.status);
}
static void test_standalone_accepts_the_band_edges(void)
{
strcpy(fake_settings.ap_pass, "geheim123");
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN,
"{\"type\":\"standalone\",\"enabled\":true,\"channel\":13}");
TEST_ASSERT_EQUAL_INT(202, resp.status);
TEST_ASSERT_EQUAL_UINT8(13, fake_standalone_channel);
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN,
"{\"type\":\"standalone\",\"enabled\":true,\"channel\":1}");
TEST_ASSERT_EQUAL_INT(202, resp.status);
TEST_ASSERT_EQUAL_UINT8(1, fake_standalone_channel);
}
static void test_standalone_rejects_bad_types_of_the_other_fields(void) static void test_standalone_rejects_bad_types_of_the_other_fields(void)
{ {
strcpy(fake_settings.ap_pass, "geheim123"); strcpy(fake_settings.ap_pass, "geheim123");
@@ -796,7 +880,12 @@ int main(void)
RUN_TEST(test_standalone_rejects_a_channel_outside_the_band); RUN_TEST(test_standalone_rejects_a_channel_outside_the_band);
RUN_TEST(test_standalone_accepts_a_full_request); RUN_TEST(test_standalone_accepts_a_full_request);
RUN_TEST(test_standalone_can_be_switched_off); RUN_TEST(test_standalone_can_be_switched_off);
RUN_TEST(test_standalone_reports_a_busy_radio_and_a_failed_save); RUN_TEST(test_standalone_busy_radio_answers_503_and_rolls_back);
RUN_TEST(test_standalone_rollback_restores_every_changed_field);
RUN_TEST(test_standalone_failed_save_answers_500_without_switching);
RUN_TEST(test_standalone_unspecified_fields_keep_their_stored_values);
RUN_TEST(test_standalone_length_limits);
RUN_TEST(test_standalone_accepts_the_band_edges);
RUN_TEST(test_standalone_rejects_bad_types_of_the_other_fields); 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_standalone_without_the_password);
RUN_TEST(test_settings_show_the_effective_ap_name); RUN_TEST(test_settings_show_the_effective_ap_name);