Coverage for app/backend/src/tests/test_email_localization.py: 100%

29 statements  

« prev     ^ index     » next       coverage.py v7.15.1, created at 2026-07-14 20:06 +0000

1""" 

2Guardrail tests that every email fully renders in English. 

3 

4These emails build their localization keys at runtime (sometimes dynamically from internal 

5state, e.g. a host request status), so a missing or mistyped key only blows up when that 

6specific email is sent in production. Here we render every email -- and every variant of 

7emails whose output depends on internal state -- and assert that no localization key is 

8missing. See `EmailBase.test_instances`. 

9""" 

10 

11import inspect 

12from datetime import UTC 

13 

14import pytest 

15 

16import couchers.email.emails 

17from couchers.email.blocks import ( 

18 EmailBase, 

19 EmailFooter, 

20 UnsubscribeInfo, 

21 UnsubscribeLink, 

22) 

23from couchers.email.rendering import render_email 

24from couchers.i18n import LocalizationContext 

25 

26 

27def _all_email_variants() -> list[tuple[str, EmailBase]]: 

28 variants: list[tuple[str, EmailBase]] = [] 

29 for _, email_class in inspect.getmembers( 

30 couchers.email.emails, lambda o: inspect.isclass(o) and o.__base__ == EmailBase 

31 ): 

32 instances = email_class.test_instances() 

33 for i, instance in enumerate(instances): 

34 variant_id = email_class.__name__ if len(instances) == 1 else f"{email_class.__name__}-{i}" 

35 variants.append((variant_id, instance)) 

36 return variants 

37 

38 

39_VARIANTS = _all_email_variants() 

40 

41_FOOTER = EmailFooter( 

42 timezone_name="UTC", 

43 unsubscribe_info=UnsubscribeInfo( 

44 manage_notifications_url="https://example.com/manage-notifications", 

45 do_not_email_url="https://example.com/do-not-email", 

46 topic_action_link=UnsubscribeLink(text="topic-action", url="https://example.com/unsubscribe"), 

47 ), 

48) 

49 

50 

51@pytest.fixture(autouse=True) 

52def _(testconfig): 

53 pass 

54 

55 

56@pytest.mark.parametrize("email", [v for _, v in _VARIANTS], ids=[i for i, _ in _VARIANTS]) 

57def test_email_renders_in_english(email: EmailBase): 

58 loc_context = LocalizationContext(locale="en", timezone=UTC) 

59 

60 # Render all email strings, since each resolve their own keys. 

61 rendered = render_email(email, _FOOTER, loc_context) 

62 assert rendered.subject 

63 assert "<" not in rendered.subject, "Subject line shoudn't contain HTML" 

64 assert rendered.body_plaintext 

65 assert rendered.body_html 

66 assert len(rendered.html_image_parts) > 0, "Images should have been embedded"