mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
fix(push): say why a test push failed, under its own title
The Web Push test answered a refused push with `ok: false` and the sender's `reason`, but the Test button reads `meta.errorCode`, so it only said "Test failed". The answer now carries `upstream_rejected`, or `connection_failed` when no push service answered. The test push used the administrator's test title, which put the brand first in several languages; it has its own title now.
This commit is contained in:
1 parent
d5980bdaee
commit
e59ef4533d
14 files changed
+67
-16
No files matched your search
+2
-1
@@ -9289,7 +9289,8 @@
|
||||
},
|
||||
"user": {
|
||||
"telegramTestBody": "HealthLog: Verbindung hergestellt. Telegram-Benachrichtigungen sind aktiv.",
|
||||
"webPushTestBody": "Wenn du das siehst, kommen Push-Benachrichtigungen auf diesem Gerät an."
|
||||
"webPushTestBody": "Wenn du das siehst, kommen Push-Benachrichtigungen auf diesem Gerät an.",
|
||||
"webPushTestTitle": "Test-Benachrichtigung von HealthLog"
|
||||
},
|
||||
"moodReminder": {
|
||||
"title": "Tägliche Stimmungs-Erinnerung",
|
||||
|
||||
+2
-1
@@ -9289,7 +9289,8 @@
|
||||
},
|
||||
"user": {
|
||||
"telegramTestBody": "HealthLog: connection successful. Telegram notifications are active.",
|
||||
"webPushTestBody": "If you can see this, push notifications reach this device."
|
||||
"webPushTestBody": "If you can see this, push notifications reach this device.",
|
||||
"webPushTestTitle": "HealthLog test notification"
|
||||
},
|
||||
"moodReminder": {
|
||||
"title": "Daily mood reminder",
|
||||
|
||||
+2
-1
@@ -9289,7 +9289,8 @@
|
||||
},
|
||||
"user": {
|
||||
"telegramTestBody": "HealthLog: conexión correcta. Las notificaciones de Telegram están activas.",
|
||||
"webPushTestBody": "Si ves esto, las notificaciones push llegan a este dispositivo."
|
||||
"webPushTestBody": "Si ves esto, las notificaciones push llegan a este dispositivo.",
|
||||
"webPushTestTitle": "Notificación de prueba de HealthLog"
|
||||
},
|
||||
"moodReminder": {
|
||||
"title": "Recordatorio diario de ánimo",
|
||||
|
||||
+2
-1
@@ -9289,7 +9289,8 @@
|
||||
},
|
||||
"user": {
|
||||
"telegramTestBody": "HealthLog: connexion réussie. Les notifications Telegram sont actives.",
|
||||
"webPushTestBody": "Si vous voyez ceci, les notifications push arrivent sur cet appareil."
|
||||
"webPushTestBody": "Si vous voyez ceci, les notifications push arrivent sur cet appareil.",
|
||||
"webPushTestTitle": "Notification de test HealthLog"
|
||||
},
|
||||
"moodReminder": {
|
||||
"title": "Rappel quotidien d’humeur",
|
||||
|
||||
+2
-1
@@ -9289,7 +9289,8 @@
|
||||
},
|
||||
"user": {
|
||||
"telegramTestBody": "HealthLog: connessione riuscita. Le notifiche Telegram sono attive.",
|
||||
"webPushTestBody": "Se vedi questo messaggio, le notifiche push arrivano su questo dispositivo."
|
||||
"webPushTestBody": "Se vedi questo messaggio, le notifiche push arrivano su questo dispositivo.",
|
||||
"webPushTestTitle": "Notifica di prova di HealthLog"
|
||||
},
|
||||
"moodReminder": {
|
||||
"title": "Promemoria quotidiano dell’umore",
|
||||
|
||||
+2
-1
@@ -9289,7 +9289,8 @@
|
||||
},
|
||||
"user": {
|
||||
"telegramTestBody": "HealthLog: 연결에 성공했어요. Telegram 알림이 켜져 있어요.",
|
||||
"webPushTestBody": "이 알림이 보이면 이 기기에서 푸시 알림을 받을 수 있어요."
|
||||
"webPushTestBody": "이 알림이 보이면 이 기기에서 푸시 알림을 받을 수 있어요.",
|
||||
"webPushTestTitle": "HealthLog 테스트 알림"
|
||||
},
|
||||
"moodReminder": {
|
||||
"title": "매일 기분 알림",
|
||||
|
||||
+2
-1
@@ -9289,7 +9289,8 @@
|
||||
},
|
||||
"user": {
|
||||
"telegramTestBody": "HealthLog: verbinding geslaagd. Telegram-meldingen zijn actief.",
|
||||
"webPushTestBody": "Zie je dit, dan komen pushmeldingen aan op dit apparaat."
|
||||
"webPushTestBody": "Zie je dit, dan komen pushmeldingen aan op dit apparaat.",
|
||||
"webPushTestTitle": "Testmelding van HealthLog"
|
||||
},
|
||||
"moodReminder": {
|
||||
"title": "Dagelijkse stemmingsherinnering",
|
||||
|
||||
+2
-1
@@ -9289,7 +9289,8 @@
|
||||
},
|
||||
"user": {
|
||||
"telegramTestBody": "HealthLog: połączenie udane. Powiadomienia Telegram są aktywne.",
|
||||
"webPushTestBody": "Jeśli to widzisz, powiadomienia push docierają na to urządzenie."
|
||||
"webPushTestBody": "Jeśli to widzisz, powiadomienia push docierają na to urządzenie.",
|
||||
"webPushTestTitle": "Powiadomienie testowe HealthLog"
|
||||
},
|
||||
"moodReminder": {
|
||||
"title": "Codzienne przypomnienie o nastroju",
|
||||
|
||||
+2
-1
@@ -9289,7 +9289,8 @@
|
||||
},
|
||||
"user": {
|
||||
"telegramTestBody": "HealthLog: conexão bem-sucedida. As notificações do Telegram estão ativas.",
|
||||
"webPushTestBody": "Se você está vendo isto, as notificações push chegam a este dispositivo."
|
||||
"webPushTestBody": "Se você está vendo isto, as notificações push chegam a este dispositivo.",
|
||||
"webPushTestTitle": "Notificação de teste do HealthLog"
|
||||
},
|
||||
"moodReminder": {
|
||||
"title": "Lembrete diário de humor",
|
||||
|
||||
+2
-1
@@ -9289,7 +9289,8 @@
|
||||
},
|
||||
"user": {
|
||||
"telegramTestBody": "HealthLog: подключение успешно. Уведомления в Telegram активны.",
|
||||
"webPushTestBody": "Если вы видите это сообщение, push-уведомления приходят на это устройство."
|
||||
"webPushTestBody": "Если вы видите это сообщение, push-уведомления приходят на это устройство.",
|
||||
"webPushTestTitle": "Тестовое уведомление HealthLog"
|
||||
},
|
||||
"moodReminder": {
|
||||
"title": "Ежедневное напоминание о настроении",
|
||||
|
||||
@@ -134,7 +134,7 @@ describe("POST /api/notifications/web-push/test", () => {
|
||||
}
|
||||
expect(payloadsSent()).toEqual([
|
||||
expect.objectContaining({
|
||||
title: "HealthLog Test-Benachrichtigung",
|
||||
title: "Test-Benachrichtigung von HealthLog",
|
||||
body: "Wenn du das siehst, kommen Push-Benachrichtigungen auf diesem Gerät an.",
|
||||
tag: "self-test",
|
||||
}),
|
||||
@@ -181,6 +181,21 @@ describe("POST /api/notifications/web-push/test", () => {
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(JSON.parse(text).data.ok).toBe(false);
|
||||
// What the Test button reads to say why.
|
||||
expect(JSON.parse(text).meta.errorCode).toBe("upstream_rejected");
|
||||
expect(text).not.toMatch(/phone-secret|laptop-secret/);
|
||||
});
|
||||
|
||||
it("tells a push service that never answered apart from one that refused", async () => {
|
||||
mocks.safeFetch.mockRejectedValue(new TypeError("fetch failed"));
|
||||
|
||||
const res = await post();
|
||||
const body = (await res.json()) as {
|
||||
data: { ok: boolean };
|
||||
meta?: { errorCode?: string };
|
||||
};
|
||||
|
||||
expect(body.data.ok).toBe(false);
|
||||
expect(body.meta?.errorCode).toBe("connection_failed");
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
||||
import { apiSuccess, apiError } from "@/lib/api-response";
|
||||
import { apiSuccess, apiSuccessWithMeta, apiError } from "@/lib/api-response";
|
||||
import { checkRateLimit } from "@/lib/rate-limit";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import { coerceLocale } from "@/lib/i18n/config";
|
||||
@@ -34,7 +34,7 @@ export const POST = apiHandler(async (request: NextRequest) => {
|
||||
const result = await sendViaWebPush(user.id, {
|
||||
eventType: "SYSTEM_ALERT",
|
||||
userId: user.id,
|
||||
title: t("notifications.admin.testNotificationTitle"),
|
||||
title: t("notifications.user.webPushTestTitle"),
|
||||
message: t("notifications.user.webPushTestBody"),
|
||||
metadata: { webPushTag: "self-test" },
|
||||
});
|
||||
@@ -54,7 +54,15 @@ export const POST = apiHandler(async (request: NextRequest) => {
|
||||
annotate({
|
||||
meta: { web_push_test_ok: result.ok, web_push_test_reason: result.reason },
|
||||
});
|
||||
return apiSuccess(
|
||||
result.ok ? { ok: true, latencyMs } : { ok: false, reason: result.reason },
|
||||
if (result.ok) return apiSuccess({ ok: true, latencyMs });
|
||||
// The Test button says what went wrong from `meta.errorCode`, like every
|
||||
// other channel's test; `reason` is the sender's own code.
|
||||
return apiSuccessWithMeta(
|
||||
{ ok: false, reason: result.reason },
|
||||
{
|
||||
errorCode: result.reason?.endsWith("_network_error")
|
||||
? "connection_failed"
|
||||
: "upstream_rejected",
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -101,6 +101,24 @@ describe("runConnectionTest", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("reads why a push test that ran was refused", async () => {
|
||||
// The Web Push test answers 200 with `ok: false`; the code rides `meta`.
|
||||
apiFetchRawMock.mockResolvedValue(
|
||||
envelope(200, {
|
||||
data: { ok: false, reason: "web_push_403" },
|
||||
error: null,
|
||||
meta: { errorCode: "upstream_rejected" },
|
||||
}),
|
||||
);
|
||||
|
||||
const result = await runConnectionTest("/api/notifications/web-push/test");
|
||||
|
||||
expect(result).toMatchObject({
|
||||
kind: "error",
|
||||
errorCode: "upstream_rejected",
|
||||
});
|
||||
});
|
||||
|
||||
it("reports success with the latency", async () => {
|
||||
apiFetchRawMock.mockResolvedValue(
|
||||
envelope(200, { data: { ok: true, latencyMs: 42 }, error: null }),
|
||||
|
||||
@@ -191,7 +191,7 @@ export const notificationTransportPaths: NonNullable<
|
||||
summary: "Send a test push to every Web Push subscription",
|
||||
description:
|
||||
"Fires one notification to each of the caller's subscriptions, through the same sender real reminders use, with the text in the account's language. The device the test was pressed on gets it whichever subscription is newest. Endpoints the push service reports gone (404/410) are removed, as on a real send. Takes no body. Rate-limited 5 per minute per user.\n\n" +
|
||||
"Read `ok`, not the status. A push that every service rejects still answers 200, with `ok: false` and a `reason`. No endpoint is returned: the full endpoint is the routing secret for that subscription.",
|
||||
"Read `ok`, not the status. A push that every service rejects still answers 200, with `ok: false`, a `reason`, and `meta.errorCode`: `upstream_rejected`, or `connection_failed` when no push service answered. No endpoint is returned: the full endpoint is the routing secret for that subscription.",
|
||||
responses: {
|
||||
"200": {
|
||||
description:
|
||||
|
||||
Reference in new issue
Block a user