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

29 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-05-29 04:01 +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.dummy_variants`. 

9""" 

10 

11import inspect 

12from datetime import UTC 

13 

14import pytest 

15 

16import couchers.email.emails 

17from couchers.email.emails import EmailBase 

18from couchers.email.rendering import ( 

19 EmailFooter, 

20 UnsubscribeInfo, 

21 UnsubscribeLink, 

22 render_html_body, 

23 render_plaintext_body, 

24) 

25from couchers.i18n import LocalizationContext 

26 

27 

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

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

30 for _, email_class in inspect.getmembers( 

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

32 ): 

33 instances = email_class.dummy_variants() 

34 for i, instance in enumerate(instances): 

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

36 variants.append((variant_id, instance)) 

37 return variants 

38 

39 

40_VARIANTS = _all_email_variants() 

41 

42_FOOTER = EmailFooter( 

43 timezone_name="UTC", 

44 unsubscribe_info=UnsubscribeInfo( 

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

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

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

48 ), 

49) 

50 

51 

52@pytest.fixture(autouse=True) 

53def _(testconfig): 

54 pass 

55 

56 

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

58def test_email_renders_in_english(email: EmailBase): 

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

60 

61 subject = email.get_subject_line(loc_context) 

62 assert subject 

63 

64 preview = email.get_preview_line(loc_context) 

65 blocks = email.get_body_blocks(loc_context) 

66 

67 # Render both the html and plaintext bodies end-to-end, since each resolves its own keys. 

68 render_html_body(subject=subject, preview=preview, blocks=blocks, footer=_FOOTER, loc_context=loc_context) 

69 render_plaintext_body(blocks=blocks, footer=_FOOTER, loc_context=loc_context)