diff --git a/src/net/nl_api.c b/src/net/nl_api.c index 99ee2e5..aa502fe 100644 --- a/src/net/nl_api.c +++ b/src/net/nl_api.c @@ -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 * wie der Hostname. Wer hier einen leeren String saehe, muesste die Vorgabe * 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_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; int asp, app, chp; bool will_enable; + nl_settings_t prev = s; /* Stand vor der Aenderung, zum Zurueckrollen bei 503 */ will_enable = cJSON_IsBool(en) && cJSON_IsTrue(en); 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. */ if (ctx->save_settings(&s) != 0) { reply_error(resp, 500, "save failed"); - } else if (!ctx->standalone_configure || - ctx->standalone_configure(will_enable, s.ap_ssid, s.ap_pass, + } else if (ctx->standalone_configure(will_enable, s.ap_ssid, s.ap_pass, 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"); } else { reply(resp, 202, "{\"status\":\"standalone\"}"); diff --git a/test/test_api.c b/test/test_api.c index 565b1ce..78fcd6f 100644 --- a/test/test_api.c +++ b/test/test_api.c @@ -641,6 +641,7 @@ static void test_standalone_accepts_a_full_request(void) "\"ssid\":\"Wohnzimmerlampe\",\"channel\":6,\"pulse\":false}"); TEST_ASSERT_EQUAL_INT(202, resp.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_TRUE(fake_standalone_enabled); 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); } -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"); + fake_settings.standalone = 0; 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()); + 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; - fake_standalone_calls = 0; +static void test_standalone_rollback_restores_every_changed_field(void) +{ + 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; 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); } +/* 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) { 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_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_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_settings_show_standalone_without_the_password); RUN_TEST(test_settings_show_the_effective_ap_name);