/* Host tests for onion_notify_format (pure string path). */ #include "test_harness.h" #include "cJSON.hpp" #include #include #include #include #include static void format_msg(char *out, size_t out_sz, int wm, const char *fmt, ...) { va_list ap; va_start(ap, fmt); onion_notify_format(out, out_sz, wm, fmt, ap); va_end(ap); } static int test_notify_format_prefix(void) { char out[128]; format_msg(out, sizeof(out), 1, "hello %s", "world"); TEST_ASSERT_STREQ("[OnionHEN] hello world", out); return 0; } static int test_notify_format_truncates(void) { char out[16]; format_msg(out, sizeof(out), 1, "0123456789ABCDEFGHIJ"); /* must be NUL-terminated and start with watermark prefix */ TEST_ASSERT_TRUE(out[sizeof(out) - 1] == '\0' || strlen(out) < sizeof(out)); TEST_ASSERT_TRUE(strncmp(out, "[OnionHEN]", 10) == 0); return 0; } static int test_notify_format_no_watermark(void) { char out[128]; format_msg(out, sizeof(out), 0, "hello %s", "world"); TEST_ASSERT_STREQ("hello world", out); return 0; } static int test_notify_send_noop(void) { /* hits sceKernelSendNotificationRequest stub — must not crash */ onion_notify(1, "host test notify %d", 7); onion_notify_debug(0, "host test debug notify %d", 7); return 0; } static unsigned char g_debug_notify_request[0xC30]; static size_t g_debug_notify_request_size; static int32_t capture_debug_notify(int32_t device, void *request, size_t size, int32_t blocking) { (void)device; (void)blocking; g_debug_notify_request_size = size; if (request && size <= sizeof(g_debug_notify_request)) memcpy(g_debug_notify_request, request, size); return 0; } static int test_notify_debug_localized(void) { memset(g_debug_notify_request, 0xff, sizeof(g_debug_notify_request)); g_debug_notify_request_size = 0; onion_notify_set_send(capture_debug_notify); onion_notify_set_language(ONION_NOTIFY_LANG_ZH_HANS); onion_notify_debug(/*show_watermark=*/0, "notify.remote_play.pairing_cancelled"); TEST_ASSERT_TRUE(g_debug_notify_request_size == sizeof(g_debug_notify_request)); TEST_ASSERT_TRUE(g_debug_notify_request[0x2C] == 0); TEST_ASSERT_STREQ("远程游玩配对已中止。", (const char *)&g_debug_notify_request[0x2D]); TEST_ASSERT_TRUE(g_debug_notify_request[0x42D] == '\0'); onion_notify_set_language(ONION_NOTIFY_LANG_EN); onion_notify_debug(/*show_watermark=*/0, "notify.remote_play.paired"); TEST_ASSERT_TRUE(g_debug_notify_request[0x2C] == 0); TEST_ASSERT_STREQ("Remote Play device paired.", (const char *)&g_debug_notify_request[0x2D]); onion_notify_debug(/*show_watermark=*/1, "notify.boot.thanks"); TEST_ASSERT_TRUE(g_debug_notify_request[0x2C] == 0); TEST_ASSERT_STREQ("[OnionHEN] Special Thanks to: kvnhrt, Modmycon", (const char *)&g_debug_notify_request[0x2D]); TEST_ASSERT_TRUE(g_debug_notify_request[0x42D] == '\0'); onion_notify_set_send(NULL); return 0; } static int test_notify_language_resolution(void) { TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_ZH_HANS, onion_notify_resolve_language(0, 11)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_ZH_HANS, onion_notify_resolve_language(1, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_EN, onion_notify_resolve_language(2, 11)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_AR, onion_notify_resolve_language(3, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_ZH_HANT, onion_notify_resolve_language(4, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_JA, onion_notify_resolve_language(5, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_FR, onion_notify_resolve_language(6, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_DE, onion_notify_resolve_language(7, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_KO, onion_notify_resolve_language(8, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_ES, onion_notify_resolve_language(9, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_PT_BR, onion_notify_resolve_language(10, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_IT, onion_notify_resolve_language(11, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_RU, onion_notify_resolve_language(12, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_PL, onion_notify_resolve_language(13, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_TH, onion_notify_resolve_language(14, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_NL, onion_notify_resolve_language(15, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_FI, onion_notify_resolve_language(16, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_SV, onion_notify_resolve_language(17, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_DA, onion_notify_resolve_language(18, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_NO, onion_notify_resolve_language(19, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_TR, onion_notify_resolve_language(20, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_CS, onion_notify_resolve_language(21, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_HU, onion_notify_resolve_language(22, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_EL, onion_notify_resolve_language(23, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_RO, onion_notify_resolve_language(24, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_VI, onion_notify_resolve_language(25, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_ID, onion_notify_resolve_language(26, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_UK, onion_notify_resolve_language(27, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_PT_PT, onion_notify_resolve_language(28, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_AR, onion_notify_resolve_language(0, 21)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_JA, onion_notify_resolve_language(0, 0)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_FR, onion_notify_resolve_language(0, 2)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_FR, onion_notify_resolve_language(0, 22)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_DE, onion_notify_resolve_language(0, 4)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_ZH_HANT, onion_notify_resolve_language(0, 10)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_ZH_HANS, onion_notify_resolve_language(0, 11)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_EN, onion_notify_resolve_language(0, 1)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_KO, onion_notify_resolve_language(0, 9)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_ES, onion_notify_resolve_language(0, 3)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_ES, onion_notify_resolve_language(0, 20)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_PT_BR, onion_notify_resolve_language(0, 17)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_PT_PT, onion_notify_resolve_language(0, 7)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_NL, onion_notify_resolve_language(0, 6)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_FI, onion_notify_resolve_language(0, 12)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_SV, onion_notify_resolve_language(0, 13)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_DA, onion_notify_resolve_language(0, 14)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_NO, onion_notify_resolve_language(0, 15)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_TR, onion_notify_resolve_language(0, 19)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_CS, onion_notify_resolve_language(0, 23)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_HU, onion_notify_resolve_language(0, 24)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_EL, onion_notify_resolve_language(0, 25)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_RO, onion_notify_resolve_language(0, 26)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_VI, onion_notify_resolve_language(0, 28)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_ID, onion_notify_resolve_language(0, 29)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_UK, onion_notify_resolve_language(0, 30)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_IT, onion_notify_resolve_language(0, 5)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_RU, onion_notify_resolve_language(0, 8)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_PL, onion_notify_resolve_language(0, 16)); TEST_ASSERT_EQ_INT(ONION_NOTIFY_LANG_TH, onion_notify_resolve_language(0, 27)); return 0; } static int test_notify_format_localized(void) { char out[128]; onion_notify_set_language(ONION_NOTIFY_LANG_ZH_HANS); format_msg(out, sizeof(out), 1, "notify.priv.unable"); TEST_ASSERT_STREQ("[OnionHEN] 无法提升权限", out); onion_notify_set_language(ONION_NOTIFY_LANG_EN); format_msg(out, sizeof(out), 1, "notify.priv.unable"); TEST_ASSERT_STREQ("[OnionHEN] Unable to raise privileges", out); onion_notify_set_language(ONION_NOTIFY_LANG_AR); format_msg(out, sizeof(out), 1, "notify.priv.unable"); TEST_ASSERT_STREQ("[OnionHEN] تعذّر رفع الامتيازات", out); onion_notify_set_language(ONION_NOTIFY_LANG_ZH_HANT); format_msg(out, sizeof(out), 1, "notify.priv.unable"); TEST_ASSERT_STREQ("[OnionHEN] 無法提升權限", out); onion_notify_set_language(ONION_NOTIFY_LANG_JA); format_msg(out, sizeof(out), 1, "notify.priv.unable"); TEST_ASSERT_STREQ("[OnionHEN] 権限を昇格できません", out); onion_notify_set_language(ONION_NOTIFY_LANG_FR); format_msg(out, sizeof(out), 1, "notify.priv.unable"); TEST_ASSERT_STREQ("[OnionHEN] Impossible d’élever les privilèges", out); onion_notify_set_language(ONION_NOTIFY_LANG_DE); format_msg(out, sizeof(out), 1, "notify.priv.unable"); TEST_ASSERT_STREQ("[OnionHEN] Berechtigungen konnten nicht erhöht werden", out); onion_notify_set_language(ONION_NOTIFY_LANG_KO); format_msg(out, sizeof(out), 1, "notify.priv.unable"); TEST_ASSERT_STREQ("[OnionHEN] 권한을 올릴 수 없습니다", out); onion_notify_set_language(ONION_NOTIFY_LANG_ES); format_msg(out, sizeof(out), 1, "notify.priv.unable"); TEST_ASSERT_STREQ("[OnionHEN] No se pudieron elevar los privilegios", out); onion_notify_set_language(ONION_NOTIFY_LANG_PT_BR); format_msg(out, sizeof(out), 1, "notify.priv.unable"); TEST_ASSERT_STREQ("[OnionHEN] Não foi possível elevar os privilégios", out); onion_notify_set_language(ONION_NOTIFY_LANG_IT); format_msg(out, sizeof(out), 1, "notify.priv.unable"); TEST_ASSERT_STREQ("[OnionHEN] Impossibile elevare i privilegi", out); onion_notify_set_language(ONION_NOTIFY_LANG_RU); format_msg(out, sizeof(out), 1, "notify.priv.unable"); TEST_ASSERT_STREQ("[OnionHEN] Не удалось повысить привилегии", out); onion_notify_set_language(ONION_NOTIFY_LANG_PL); format_msg(out, sizeof(out), 1, "notify.priv.unable"); TEST_ASSERT_STREQ("[OnionHEN] Nie udało się podnieść uprawnień", out); onion_notify_set_language(ONION_NOTIFY_LANG_TH); format_msg(out, sizeof(out), 1, "notify.priv.unable"); TEST_ASSERT_STREQ("[OnionHEN] ไม่สามารถยกระดับสิทธิ์ได้", out); onion_notify_set_language(ONION_NOTIFY_LANG_NL); format_msg(out, sizeof(out), 1, "notify.priv.unable"); TEST_ASSERT_STREQ("[OnionHEN] Rechten konden niet worden verhoogd", out); onion_notify_set_language(ONION_NOTIFY_LANG_TR); format_msg(out, sizeof(out), 1, "notify.priv.unable"); TEST_ASSERT_STREQ("[OnionHEN] Yetkiler yükseltilemedi", out); onion_notify_set_language(ONION_NOTIFY_LANG_VI); format_msg(out, sizeof(out), 1, "notify.priv.unable"); TEST_ASSERT_STREQ("[OnionHEN] Không nâng được quyền", out); onion_notify_set_language(ONION_NOTIFY_LANG_UK); format_msg(out, sizeof(out), 1, "notify.priv.unable"); TEST_ASSERT_STREQ("[OnionHEN] Не вдалося підвищити права", out); onion_notify_set_language(ONION_NOTIFY_LANG_PT_PT); format_msg(out, sizeof(out), 1, "notify.priv.unable"); TEST_ASSERT_STREQ("[OnionHEN] Não foi possível elevar os privilégios", out); return 0; } static int test_notify_payload_localized(void) { char out[256]; onion_notify_set_language(ONION_NOTIFY_LANG_ZH_HANS); format_msg(out, sizeof(out), 1, "notify.payload.loading", "demo.elf"); TEST_ASSERT_STREQ("[OnionHEN] 正在加载 Payload demo.elf...", out); format_msg(out, sizeof(out), 1, "notify.payload.launched", "/data/demo.elf", "demo"); TEST_ASSERT_STREQ( "[OnionHEN] Payload 已启动\n路径:/data/demo.elf\n标识:demo", out); onion_notify_set_language(ONION_NOTIFY_LANG_EN); return 0; } static char g_rich_payload[4096]; static int32_t capture_rich_notify(int32_t user_id, bool is_logged, const char *payload) { TEST_ASSERT_TRUE(user_id == 0xFE); TEST_ASSERT_TRUE(is_logged); snprintf(g_rich_payload, sizeof(g_rich_payload), "%s", payload ? payload : ""); return 0; } static int test_notify_rich_formats_payload(void) { g_rich_payload[0] = '\0'; onion_notify_set_rich_send(capture_rich_notify); onion_notify_rich("Title", "Sub \"quoted\"", "/icon\"quoted.png", "download", "42"); cJSON *root = cJSON_Parse(g_rich_payload); cJSON *raw = cJSON_GetObjectItemCaseSensitive(root, "rawData"); cJSON *view = cJSON_GetObjectItemCaseSensitive(raw, "viewData"); cJSON *message = cJSON_GetObjectItemCaseSensitive(view, "message"); cJSON *sub_message = cJSON_GetObjectItemCaseSensitive(view, "subMessage"); cJSON *icon = cJSON_GetObjectItemCaseSensitive(view, "icon"); cJSON *icon_params = cJSON_GetObjectItemCaseSensitive(icon, "parameters"); TEST_ASSERT_TRUE(cJSON_IsObject(root)); TEST_ASSERT_STREQ("InteractiveToastTemplateB", cJSON_GetObjectItemCaseSensitive(raw, "viewTemplateType") ->valuestring); TEST_ASSERT_STREQ( "Title", cJSON_GetObjectItemCaseSensitive(message, "body")->valuestring); TEST_ASSERT_STREQ("Sub \"quoted\"", cJSON_GetObjectItemCaseSensitive(sub_message, "body") ->valuestring); TEST_ASSERT_STREQ("/icon\"quoted.png", cJSON_GetObjectItemCaseSensitive(icon_params, "url") ->valuestring); TEST_ASSERT_STREQ("42", cJSON_GetObjectItemCaseSensitive(root, "localNotificationId") ->valuestring); cJSON_Delete(root); return 0; } static int test_notify_rich_localizes_both_text_fields(void) { g_rich_payload[0] = '\0'; onion_notify_set_language(ONION_NOTIFY_LANG_ZH_HANS); onion_notify_set_rich_send(capture_rich_notify); onion_notify_rich("notify.brand", "notify.boot.starting", "/icon.png", "download", "43"); TEST_ASSERT_TRUE(strstr(g_rich_payload, "OnionHEN 正在启动...") != NULL); onion_notify_set_language(ONION_NOTIFY_LANG_EN); return 0; } static int g_plain_fallback_calls; static char g_plain_fallback_message[1024]; static int g_rich_fail_calls; static int32_t capture_plain_fallback(int32_t device, void *request, size_t size, int32_t blocking) { (void)device; (void)blocking; g_plain_fallback_calls++; memset(g_plain_fallback_message, 0, sizeof(g_plain_fallback_message)); if (request && size >= 0x2D + 1) { snprintf(g_plain_fallback_message, sizeof(g_plain_fallback_message), "%s", (const char *)request + 0x2D); } return 0; } static int32_t fail_rich_notify(int32_t user_id, bool is_logged, const char *payload) { (void)user_id; (void)is_logged; (void)payload; g_rich_fail_calls++; return -1; } static int test_notify_rich_falls_back_when_send_fails(void) { g_plain_fallback_calls = 0; g_rich_fail_calls = 0; g_plain_fallback_message[0] = '\0'; onion_notify_set_send(capture_plain_fallback); onion_notify_set_rich_send(fail_rich_notify); onion_notify_rich("Title", "Sub", "/icon.png", "download", "44"); TEST_ASSERT_EQ_INT(1, g_rich_fail_calls); TEST_ASSERT_EQ_INT(1, g_plain_fallback_calls); TEST_ASSERT_STREQ("Sub", g_plain_fallback_message); onion_notify_set_send(NULL); onion_notify_set_rich_send(NULL); return 0; } static int test_notify_rich_falls_back_when_unregistered(void) { g_plain_fallback_calls = 0; g_plain_fallback_message[0] = '\0'; onion_notify_set_send(capture_plain_fallback); onion_notify_set_rich_send(NULL); onion_notify_rich("Title", "", "/icon.png", "download", "45"); TEST_ASSERT_EQ_INT(1, g_plain_fallback_calls); TEST_ASSERT_STREQ("Title", g_plain_fallback_message); onion_notify_set_send(NULL); return 0; } static int test_notify_rich_skips_send_without_icon(void) { g_plain_fallback_calls = 0; g_rich_fail_calls = 0; g_plain_fallback_message[0] = '\0'; onion_notify_set_send(capture_plain_fallback); onion_notify_set_rich_send(fail_rich_notify); onion_notify_rich("notify.brand", "notify.boot.starting", "", "download", "46"); TEST_ASSERT_EQ_INT(0, g_rich_fail_calls); TEST_ASSERT_EQ_INT(1, g_plain_fallback_calls); TEST_ASSERT_STREQ("OnionHEN is starting...", g_plain_fallback_message); onion_notify_set_send(NULL); onion_notify_set_rich_send(NULL); return 0; } static int test_notify_try_rich_falls_back_on_empty_payload(void) { g_plain_fallback_calls = 0; g_rich_fail_calls = 0; g_plain_fallback_message[0] = '\0'; onion_notify_set_send(capture_plain_fallback); onion_notify_set_rich_send(fail_rich_notify); onion_notify_try_rich(NULL, "notify.boot.welcome"); TEST_ASSERT_EQ_INT(0, g_rich_fail_calls); TEST_ASSERT_EQ_INT(1, g_plain_fallback_calls); TEST_ASSERT_STREQ("Welcome to OnionHEN", g_plain_fallback_message); onion_notify_set_send(NULL); onion_notify_set_rich_send(NULL); return 0; } int test_platform_notify_suite(void) { int failures = 0; failures += onion_test_run("notify_format_prefix", test_notify_format_prefix); failures += onion_test_run("notify_format_truncates", test_notify_format_truncates); failures += onion_test_run("notify_format_no_watermark", test_notify_format_no_watermark); failures += onion_test_run("notify_send_noop", test_notify_send_noop); failures += onion_test_run("notify_debug_localized", test_notify_debug_localized); failures += onion_test_run("notify_language_resolution", test_notify_language_resolution); failures += onion_test_run("notify_format_localized", test_notify_format_localized); failures += onion_test_run("notify_payload_localized", test_notify_payload_localized); failures += onion_test_run("notify_rich_formats_payload", test_notify_rich_formats_payload); failures += onion_test_run("notify_rich_localizes_both_text_fields", test_notify_rich_localizes_both_text_fields); failures += onion_test_run("notify_rich_falls_back_when_send_fails", test_notify_rich_falls_back_when_send_fails); failures += onion_test_run("notify_rich_falls_back_when_unregistered", test_notify_rich_falls_back_when_unregistered); failures += onion_test_run("notify_rich_skips_send_without_icon", test_notify_rich_skips_send_without_icon); failures += onion_test_run("notify_try_rich_falls_back_on_empty_payload", test_notify_try_rich_falls_back_on_empty_payload); return failures; }