diff --git a/backend/tests/test_email_alerts.py b/backend/tests/test_email_alerts.py index 3eddb99a..f8b66c94 100644 --- a/backend/tests/test_email_alerts.py +++ b/backend/tests/test_email_alerts.py @@ -102,6 +102,40 @@ def test_no_admins_means_no_notification_and_no_error(db, make_user): assert _notifications(db, plain.id) == [] +def test_a_drop_with_nobody_to_tell_does_not_burn_the_hour(db, make_user): + # The throttle window must only be spent on a notice that was actually written. Otherwise a drop + # during setup — before any admin exists — silences the next hour for the admin created a minute + # later, which is exactly the blind spot this alert exists to remove. + email_mod.send_test("someone@example.com") # no admins yet + admin = make_user(email="admin@example.com", role="admin") + + assert email_mod.send_test("someone@example.com") is False + + db.expire_all() + assert len(_notifications(db, admin.id)) == 1 + + +def test_a_failed_alert_does_not_burn_the_hour_either(db, make_user, monkeypatch): + admin = make_user(email="admin@example.com", role="admin") + boom = {"on": True} + + def _maybe_boom(*a, **kw): + if boom["on"]: + raise RuntimeError("notification store is down") + from app.notifications import create_notification as real + + return real(*a, **kw) + + monkeypatch.setattr(email_mod, "create_notification", _maybe_boom) + email_mod.send_test("someone@example.com") # the write blows up; nothing recorded + boom["on"] = False + + assert email_mod.send_test("someone@example.com") is False + + db.expire_all() + assert len(_notifications(db, admin.id)) == 1 + + def test_a_broken_alert_never_breaks_the_send(db, make_user, monkeypatch): # The alert is the fallback path; if IT fails, the caller must still get the ordinary fail-soft # False rather than an exception surfacing from a background task. diff --git a/frontend/src/components/adminInvites.dom.test.tsx b/frontend/src/components/adminInvites.dom.test.tsx index 41686a1f..1e3cda1a 100644 --- a/frontend/src/components/adminInvites.dom.test.tsx +++ b/frontend/src/components/adminInvites.dom.test.tsx @@ -122,6 +122,30 @@ describe("Access requests — the delete affordance", () => { expect(screen.queryByLabelText(/deleteLabel/)).toBeNull(); }); + it("keeps the same control on the previously-decided list, where the case is commonest", async () => { + // The affordance exists twice — the pending rows and the collapsed "decided" list — and an + // approved address that has since registered lands in the second one. Both must behave alike. + listMock.mockResolvedValue([invite({ status: "approved", has_account: true })]); + renderPanel(); + await openAccessTab(); + + fireEvent.click(await screen.findByText(/settings\.invites\.decided/)); + const btn = await screen.findByLabelText("settings.invites.deleteHasAccount"); + expect(btn.getAttribute("aria-disabled")).toBe("true"); + expect((btn as HTMLButtonElement).disabled).toBe(false); + }); + + it("offers a working delete on a decided entry with no account", async () => { + listMock.mockResolvedValue([invite({ status: "denied" })]); + renderPanel(); + await openAccessTab(); + + fireEvent.click(await screen.findByText(/settings\.invites\.decided/)); + fireEvent.click(await screen.findByLabelText("settings.invites.deleteLabel:asker@example.com")); + + await waitFor(() => expect(deleteMock).toHaveBeenCalledWith(1)); + }); + it("warns in the confirmation — before the click is irreversible — when the env still allows it", async () => { listMock.mockResolvedValue([invite({ still_allowed_by_env: true })]); renderPanel(); diff --git a/frontend/src/i18n/locales/en/inbox.json b/frontend/src/i18n/locales/en/inbox.json index 1373bb92..56e97178 100644 --- a/frontend/src/i18n/locales/en/inbox.json +++ b/frontend/src/i18n/locales/en/inbox.json @@ -32,6 +32,6 @@ "emailUndelivered": { "title": "Email is not going out", "notConfigured": "SMTP is not configured, so nothing this instance sends is being delivered — verification links, password resets and access requests alike. One of them was “{{subject}}”. Set it up under Configuration → Email / SMTP.", - "sendFailed": "The mail server rejected a message, so deliveries are failing. One of them was “{{subject}}”. Check Configuration → Email / SMTP and send a test." + "sendFailed": "Sending failed, so messages are not being delivered. One of them was “{{subject}}”. Check Configuration → Email / SMTP and send a test; the server log has the reason." } } diff --git a/frontend/src/i18n/locales/en/settings.json b/frontend/src/i18n/locales/en/settings.json index 6778ac43..96d12b5d 100644 --- a/frontend/src/i18n/locales/en/settings.json +++ b/frontend/src/i18n/locales/en/settings.json @@ -169,12 +169,12 @@ "deleteLabel": "Delete {{email}} from the whitelist", "deleteHint": "Forget this address. A later request from it comes back as a new one.", "deleteTitle": "Delete whitelist entry?", - "deleteConfirm": "Forget {{email}}? Siftlode drops the record, including an approval you already gave, so anyone at that address would have to ask again.", + "deleteConfirm": "Forget {{email}}? Siftlode drops the record, so anyone at that address would have to ask again.", "deleteHasAccount": "Someone has registered on this address — manage it under Users & roles, where deleting the account removes this entry too.", "deleted": "Whitelist entry removed.", "deleteFailed": "Could not remove the entry.", - "emailOff": "Email sending is not configured, so nobody gets a verification link and no request reaches you by email — you will only see sign-ups in this list. Fix it under Configuration → Email / SMTP.", + "emailOff": "Email sending is not configured, so nobody gets a verification link and no request reaches you by email. Watch this list and your inbox instead, and fix it under Configuration → Email / SMTP.", "deletedStillAllowed": "Entry removed, but this address is still allow-listed by the server's configuration, so it keeps access. Remove it from ALLOWED_EMAILS / ADMIN_EMAILS to change that.", - "deleteConfirmStillAllowed": "Forget {{email}}? Careful: this address is also allow-listed in the server's configuration (ALLOWED_EMAILS / ADMIN_EMAILS), so removing this row will NOT take its access away — and an ADMIN_EMAILS address is made an admin again on its next sign-in." + "deleteConfirmStillAllowed": "Forget {{email}}? Careful: this address is also allow-listed in the server's configuration (ALLOWED_EMAILS / ADMIN_EMAILS), so removing this row will NOT take its access away — and an ADMIN_EMAILS address is made an admin again on its next Google sign-in." } } diff --git a/frontend/src/i18n/locales/hu/inbox.json b/frontend/src/i18n/locales/hu/inbox.json index c5b82165..6352b822 100644 --- a/frontend/src/i18n/locales/hu/inbox.json +++ b/frontend/src/i18n/locales/hu/inbox.json @@ -32,6 +32,6 @@ "emailUndelivered": { "title": "Nem megy ki e-mail", "notConfigured": "Az SMTP nincs beállítva, így semmi nem kézbesíthető, amit ez a példány küldene — megerősítő linkek, jelszó-visszaállítások és hozzáférés-kérelmek egyaránt. Az egyik ilyen: „{{subject}}”. Állítsd be a Konfiguráció → E-mail / SMTP fülön.", - "sendFailed": "A levelezőszerver visszautasított egy üzenetet, tehát a kézbesítés hibára fut. Az egyik ilyen: „{{subject}}”. Nézd meg a Konfiguráció → E-mail / SMTP fület, és küldj tesztet." + "sendFailed": "A küldés nem sikerült, így az üzenetek nem kézbesíthetők. Az egyik ilyen: „{{subject}}”. Nézd meg a Konfiguráció → E-mail / SMTP fület, és küldj tesztet; az okot a szerver naplója tartalmazza." } } diff --git a/frontend/src/i18n/locales/hu/settings.json b/frontend/src/i18n/locales/hu/settings.json index f6f80d3e..999d59e0 100644 --- a/frontend/src/i18n/locales/hu/settings.json +++ b/frontend/src/i18n/locales/hu/settings.json @@ -169,12 +169,12 @@ "deleteLabel": "{{email}} törlése a whitelistáról", "deleteHint": "A cím elfelejtése. Egy későbbi kérelme új kérelemként jelenik meg.", "deleteTitle": "Törlöd a whitelista-bejegyzést?", - "deleteConfirm": "Elfelejted {{email}} címet? A Siftlode eldobja a bejegyzést, a korábban megadott jóváhagyással együtt, így az adott címről újra kérelmezni kell.", + "deleteConfirm": "Elfelejted {{email}} címet? A Siftlode eldobja a bejegyzést, így az adott címről újra kérelmezni kell.", "deleteHasAccount": "Erre a címre már regisztrált valaki — a Felhasználók és szerepek fülön kezelhető, ahol a fiók törlése ezt a bejegyzést is eltávolítja.", "deleted": "Whitelista-bejegyzés eltávolítva.", "deleteFailed": "A bejegyzést nem sikerült eltávolítani.", - "emailOff": "Az e-mail-küldés nincs beállítva, így senki nem kap megerősítő linket, és e-mailben egy kérelem sem jut el hozzád — a regisztrációkat csak ebben a listában látod. A Konfiguráció → E-mail / SMTP fülön javítható.", + "emailOff": "Az e-mail-küldés nincs beállítva, így senki nem kap megerősítő linket, és e-mailben egy kérelem sem jut el hozzád. Ezt a listát és az értesítéseidet figyeld helyette, a javítás pedig a Konfiguráció → E-mail / SMTP fülön van.", "deletedStillAllowed": "A bejegyzés törölve, de ezt a címet a szerver konfigurációja továbbra is engedélyezi, tehát megmarad a hozzáférése. Vedd ki az ALLOWED_EMAILS / ADMIN_EMAILS listából, ha ezen változtatnál.", - "deleteConfirmStillAllowed": "Elfelejted {{email}} címet? Figyelem: ezt a címet a szerver konfigurációja is engedélyezi (ALLOWED_EMAILS / ADMIN_EMAILS), így a bejegyzés törlése NEM veszi el a hozzáférését — sőt egy ADMIN_EMAILS cím a következő belépéskor újra adminná válik." + "deleteConfirmStillAllowed": "Elfelejted {{email}} címet? Figyelem: ezt a címet a szerver konfigurációja is engedélyezi (ALLOWED_EMAILS / ADMIN_EMAILS), így a bejegyzés törlése NEM veszi el a hozzáférését — sőt egy ADMIN_EMAILS cím a következő Google-belépéskor újra adminná válik." } }