refactor(tests): compress admin_client + mock_send_email into conftest

Lift admin_client fixture from 7 duplicate definitions into conftest.py.
Add mock_send_email fixture, replacing 60 inline patch() blocks across
test_emails.py, test_waitlist.py, and test_businessplan.py. Net -197 lines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Deeman
2026-03-02 09:40:52 +01:00
parent f93e4fd0d1
commit 162e633c62
12 changed files with 294 additions and 488 deletions

View File

@@ -50,59 +50,51 @@ def _assert_common_design(html: str, lang: str = "en"):
class TestMagicLink:
@pytest.mark.asyncio
async def test_sends_to_correct_recipient(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_magic_link({"email": "user@example.com", "token": "abc123"})
kw = _call_kwargs(mock_send)
assert kw["to"] == "user@example.com"
async def test_sends_to_correct_recipient(self, mock_send_email):
await handle_send_magic_link({"email": "user@example.com", "token": "abc123"})
kw = _call_kwargs(mock_send_email)
assert kw["to"] == "user@example.com"
@pytest.mark.asyncio
async def test_subject_contains_app_name(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_magic_link({"email": "user@example.com", "token": "abc123"})
kw = _call_kwargs(mock_send)
assert core.config.APP_NAME.lower() in kw["subject"].lower()
async def test_subject_contains_app_name(self, mock_send_email):
await handle_send_magic_link({"email": "user@example.com", "token": "abc123"})
kw = _call_kwargs(mock_send_email)
assert core.config.APP_NAME.lower() in kw["subject"].lower()
@pytest.mark.asyncio
async def test_html_contains_verify_link(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_magic_link({"email": "user@example.com", "token": "abc123"})
kw = _call_kwargs(mock_send)
assert "/auth/verify?token=abc123" in kw["html"]
async def test_html_contains_verify_link(self, mock_send_email):
await handle_send_magic_link({"email": "user@example.com", "token": "abc123"})
kw = _call_kwargs(mock_send_email)
assert "/auth/verify?token=abc123" in kw["html"]
@pytest.mark.asyncio
async def test_html_contains_fallback_link_text(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_magic_link({"email": "user@example.com", "token": "tok"})
html = _call_kwargs(mock_send)["html"]
assert "word-break:break-all" in html # fallback URL block
async def test_html_contains_fallback_link_text(self, mock_send_email):
await handle_send_magic_link({"email": "user@example.com", "token": "tok"})
html = _call_kwargs(mock_send_email)["html"]
assert "word-break:break-all" in html # fallback URL block
@pytest.mark.asyncio
async def test_uses_transactional_from_addr(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_magic_link({"email": "user@example.com", "token": "tok"})
assert _call_kwargs(mock_send)["from_addr"] == core.EMAIL_ADDRESSES["transactional"]
async def test_uses_transactional_from_addr(self, mock_send_email):
await handle_send_magic_link({"email": "user@example.com", "token": "tok"})
assert _call_kwargs(mock_send_email)["from_addr"] == core.EMAIL_ADDRESSES["transactional"]
@pytest.mark.asyncio
async def test_preheader_mentions_expiry(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_magic_link({"email": "user@example.com", "token": "tok"})
html = _call_kwargs(mock_send)["html"]
# preheader is hidden span; should mention minutes
assert "display:none" in html # preheader present
async def test_preheader_mentions_expiry(self, mock_send_email):
await handle_send_magic_link({"email": "user@example.com", "token": "tok"})
html = _call_kwargs(mock_send_email)["html"]
# preheader is hidden span; should mention minutes
assert "display:none" in html # preheader present
@pytest.mark.asyncio
async def test_design_elements_present(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_magic_link({"email": "user@example.com", "token": "tok"})
_assert_common_design(_call_kwargs(mock_send)["html"])
async def test_design_elements_present(self, mock_send_email):
await handle_send_magic_link({"email": "user@example.com", "token": "tok"})
_assert_common_design(_call_kwargs(mock_send_email)["html"])
@pytest.mark.asyncio
async def test_respects_lang_parameter(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_magic_link({"email": "user@example.com", "token": "tok", "lang": "de"})
html = _call_kwargs(mock_send)["html"]
_assert_common_design(html, lang="de")
async def test_respects_lang_parameter(self, mock_send_email):
await handle_send_magic_link({"email": "user@example.com", "token": "tok", "lang": "de"})
html = _call_kwargs(mock_send_email)["html"]
_assert_common_design(html, lang="de")
# ── Welcome ──────────────────────────────────────────────────────
@@ -110,59 +102,51 @@ class TestMagicLink:
class TestWelcome:
@pytest.mark.asyncio
async def test_sends_to_correct_recipient(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_welcome({"email": "new@example.com"})
assert _call_kwargs(mock_send)["to"] == "new@example.com"
async def test_sends_to_correct_recipient(self, mock_send_email):
await handle_send_welcome({"email": "new@example.com"})
assert _call_kwargs(mock_send_email)["to"] == "new@example.com"
@pytest.mark.asyncio
async def test_subject_not_empty(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_welcome({"email": "new@example.com"})
assert len(_call_kwargs(mock_send)["subject"]) > 5
async def test_subject_not_empty(self, mock_send_email):
await handle_send_welcome({"email": "new@example.com"})
assert len(_call_kwargs(mock_send_email)["subject"]) > 5
@pytest.mark.asyncio
async def test_html_contains_quickstart_links(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_welcome({"email": "new@example.com"})
html = _call_kwargs(mock_send)["html"]
assert "/planner" in html
assert "/markets" in html
assert "/leads/quote" in html
async def test_html_contains_quickstart_links(self, mock_send_email):
await handle_send_welcome({"email": "new@example.com"})
html = _call_kwargs(mock_send_email)["html"]
assert "/planner" in html
assert "/markets" in html
assert "/leads/quote" in html
@pytest.mark.asyncio
async def test_uses_first_name_when_provided(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_welcome({"email": "new@example.com", "name": "Alice Smith"})
html = _call_kwargs(mock_send)["html"]
assert "Alice" in html
async def test_uses_first_name_when_provided(self, mock_send_email):
await handle_send_welcome({"email": "new@example.com", "name": "Alice Smith"})
html = _call_kwargs(mock_send_email)["html"]
assert "Alice" in html
@pytest.mark.asyncio
async def test_fallback_greeting_when_no_name(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_welcome({"email": "new@example.com"})
html = _call_kwargs(mock_send)["html"]
# Should use "there" as fallback first_name
assert "there" in html.lower()
async def test_fallback_greeting_when_no_name(self, mock_send_email):
await handle_send_welcome({"email": "new@example.com"})
html = _call_kwargs(mock_send_email)["html"]
# Should use "there" as fallback first_name
assert "there" in html.lower()
@pytest.mark.asyncio
async def test_uses_transactional_from_addr(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_welcome({"email": "new@example.com"})
assert _call_kwargs(mock_send)["from_addr"] == core.EMAIL_ADDRESSES["transactional"]
async def test_uses_transactional_from_addr(self, mock_send_email):
await handle_send_welcome({"email": "new@example.com"})
assert _call_kwargs(mock_send_email)["from_addr"] == core.EMAIL_ADDRESSES["transactional"]
@pytest.mark.asyncio
async def test_design_elements_present(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_welcome({"email": "new@example.com"})
_assert_common_design(_call_kwargs(mock_send)["html"])
async def test_design_elements_present(self, mock_send_email):
await handle_send_welcome({"email": "new@example.com"})
_assert_common_design(_call_kwargs(mock_send_email)["html"])
@pytest.mark.asyncio
async def test_german_welcome(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_welcome({"email": "new@example.com", "lang": "de"})
html = _call_kwargs(mock_send)["html"]
_assert_common_design(html, lang="de")
async def test_german_welcome(self, mock_send_email):
await handle_send_welcome({"email": "new@example.com", "lang": "de"})
html = _call_kwargs(mock_send_email)["html"]
_assert_common_design(html, lang="de")
# ── Quote Verification ───────────────────────────────────────────
@@ -180,57 +164,50 @@ class TestQuoteVerification:
}
@pytest.mark.asyncio
async def test_sends_to_contact_email(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_quote_verification(self._BASE_PAYLOAD)
assert _call_kwargs(mock_send)["to"] == "lead@example.com"
async def test_sends_to_contact_email(self, mock_send_email):
await handle_send_quote_verification(self._BASE_PAYLOAD)
assert _call_kwargs(mock_send_email)["to"] == "lead@example.com"
@pytest.mark.asyncio
async def test_html_contains_verify_link(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_quote_verification(self._BASE_PAYLOAD)
html = _call_kwargs(mock_send)["html"]
assert "token=verify_tok" in html
assert "lead=lead_tok" in html
async def test_html_contains_verify_link(self, mock_send_email):
await handle_send_quote_verification(self._BASE_PAYLOAD)
html = _call_kwargs(mock_send_email)["html"]
assert "token=verify_tok" in html
assert "lead=lead_tok" in html
@pytest.mark.asyncio
async def test_html_contains_project_recap(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_quote_verification(self._BASE_PAYLOAD)
html = _call_kwargs(mock_send)["html"]
assert "6 courts" in html
assert "Indoor" in html
assert "Germany" in html
async def test_html_contains_project_recap(self, mock_send_email):
await handle_send_quote_verification(self._BASE_PAYLOAD)
html = _call_kwargs(mock_send_email)["html"]
assert "6 courts" in html
assert "Indoor" in html
assert "Germany" in html
@pytest.mark.asyncio
async def test_uses_first_name_from_contact(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_quote_verification(self._BASE_PAYLOAD)
html = _call_kwargs(mock_send)["html"]
assert "Bob" in html
async def test_uses_first_name_from_contact(self, mock_send_email):
await handle_send_quote_verification(self._BASE_PAYLOAD)
html = _call_kwargs(mock_send_email)["html"]
assert "Bob" in html
@pytest.mark.asyncio
async def test_handles_minimal_payload(self):
async def test_handles_minimal_payload(self, mock_send_email):
"""No court_count/facility_type/country — should still send."""
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_quote_verification({
"email": "lead@example.com",
"token": "tok",
"lead_token": "ltok",
})
mock_send.assert_called_once()
await handle_send_quote_verification({
"email": "lead@example.com",
"token": "tok",
"lead_token": "ltok",
})
mock_send_email.assert_called_once()
@pytest.mark.asyncio
async def test_uses_transactional_from_addr(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_quote_verification(self._BASE_PAYLOAD)
assert _call_kwargs(mock_send)["from_addr"] == core.EMAIL_ADDRESSES["transactional"]
async def test_uses_transactional_from_addr(self, mock_send_email):
await handle_send_quote_verification(self._BASE_PAYLOAD)
assert _call_kwargs(mock_send_email)["from_addr"] == core.EMAIL_ADDRESSES["transactional"]
@pytest.mark.asyncio
async def test_design_elements_present(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_quote_verification(self._BASE_PAYLOAD)
_assert_common_design(_call_kwargs(mock_send)["html"])
async def test_design_elements_present(self, mock_send_email):
await handle_send_quote_verification(self._BASE_PAYLOAD)
_assert_common_design(_call_kwargs(mock_send_email)["html"])
# ── Lead Forward (the money email) ──────────────────────────────
@@ -238,89 +215,71 @@ class TestQuoteVerification:
class TestLeadForward:
@pytest.mark.asyncio
async def test_sends_to_supplier_email(self, db):
async def test_sends_to_supplier_email(self, db, mock_send_email):
lead_id, supplier_id = await _seed_lead_and_supplier(db)
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
assert _call_kwargs(mock_send)["to"] == "supplier@test.com"
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
assert _call_kwargs(mock_send_email)["to"] == "supplier@test.com"
@pytest.mark.asyncio
async def test_subject_contains_heat_and_country(self, db):
async def test_subject_contains_heat_and_country(self, db, mock_send_email):
lead_id, supplier_id = await _seed_lead_and_supplier(db)
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
subject = _call_kwargs(mock_send)["subject"]
assert "[HOT]" in subject
assert "Germany" in subject
assert "4 courts" in subject
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
subject = _call_kwargs(mock_send_email)["subject"]
assert "[HOT]" in subject
assert "Germany" in subject
assert "4 courts" in subject
@pytest.mark.asyncio
async def test_html_contains_heat_badge(self, db):
async def test_html_contains_heat_badge(self, db, mock_send_email):
lead_id, supplier_id = await _seed_lead_and_supplier(db)
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
html = _call_kwargs(mock_send)["html"]
assert "#DC2626" in html # HOT badge color
assert "HOT" in html
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
html = _call_kwargs(mock_send_email)["html"]
assert "#DC2626" in html # HOT badge color
assert "HOT" in html
@pytest.mark.asyncio
async def test_html_contains_project_brief(self, db):
async def test_html_contains_project_brief(self, db, mock_send_email):
lead_id, supplier_id = await _seed_lead_and_supplier(db)
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
html = _call_kwargs(mock_send)["html"]
assert "Indoor" in html
assert "Germany" in html
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
html = _call_kwargs(mock_send_email)["html"]
assert "Indoor" in html
assert "Germany" in html
@pytest.mark.asyncio
async def test_html_contains_contact_info(self, db):
async def test_html_contains_contact_info(self, db, mock_send_email):
lead_id, supplier_id = await _seed_lead_and_supplier(db)
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
html = _call_kwargs(mock_send)["html"]
assert "lead@buyer.com" in html
assert "mailto:lead@buyer.com" in html
assert "John Doe" in html
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
html = _call_kwargs(mock_send_email)["html"]
assert "lead@buyer.com" in html
assert "mailto:lead@buyer.com" in html
assert "John Doe" in html
@pytest.mark.asyncio
async def test_html_contains_urgency_callout(self, db):
async def test_html_contains_urgency_callout(self, db, mock_send_email):
lead_id, supplier_id = await _seed_lead_and_supplier(db)
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
html = _call_kwargs(mock_send)["html"]
# Urgency callout has yellow background
assert "#FEF3C7" in html
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
html = _call_kwargs(mock_send_email)["html"]
# Urgency callout has yellow background
assert "#FEF3C7" in html
@pytest.mark.asyncio
async def test_html_contains_direct_reply_cta(self, db):
async def test_html_contains_direct_reply_cta(self, db, mock_send_email):
lead_id, supplier_id = await _seed_lead_and_supplier(db)
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
html = _call_kwargs(mock_send)["html"]
# Direct reply link text should mention the contact email
assert "lead@buyer.com" in html
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
html = _call_kwargs(mock_send_email)["html"]
# Direct reply link text should mention the contact email
assert "lead@buyer.com" in html
@pytest.mark.asyncio
async def test_uses_leads_from_addr(self, db):
async def test_uses_leads_from_addr(self, db, mock_send_email):
lead_id, supplier_id = await _seed_lead_and_supplier(db)
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
assert _call_kwargs(mock_send)["from_addr"] == core.EMAIL_ADDRESSES["leads"]
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
assert _call_kwargs(mock_send_email)["from_addr"] == core.EMAIL_ADDRESSES["leads"]
@pytest.mark.asyncio
async def test_updates_email_sent_at(self, db):
async def test_updates_email_sent_at(self, db, mock_send_email):
lead_id, supplier_id = await _seed_lead_and_supplier(db, create_forward=True)
with patch("padelnomics.worker.send_email", new_callable=AsyncMock):
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
async with db.execute(
"SELECT email_sent_at FROM lead_forwards WHERE lead_id = ? AND supplier_id = ?",
@@ -331,30 +290,24 @@ class TestLeadForward:
assert row["email_sent_at"] is not None
@pytest.mark.asyncio
async def test_skips_when_no_supplier_email(self, db):
async def test_skips_when_no_supplier_email(self, db, mock_send_email):
"""No email on supplier record — handler exits without sending."""
lead_id, supplier_id = await _seed_lead_and_supplier(db, supplier_email="")
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
mock_send.assert_not_called()
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
mock_send_email.assert_not_called()
@pytest.mark.asyncio
async def test_skips_when_lead_not_found(self, db):
async def test_skips_when_lead_not_found(self, db, mock_send_email):
"""Non-existent lead_id — handler exits without sending."""
_, supplier_id = await _seed_lead_and_supplier(db)
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_lead_forward_email({"lead_id": 99999, "supplier_id": supplier_id})
mock_send.assert_not_called()
await handle_send_lead_forward_email({"lead_id": 99999, "supplier_id": supplier_id})
mock_send_email.assert_not_called()
@pytest.mark.asyncio
async def test_design_elements_present(self, db):
async def test_design_elements_present(self, db, mock_send_email):
lead_id, supplier_id = await _seed_lead_and_supplier(db)
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
_assert_common_design(_call_kwargs(mock_send)["html"])
await handle_send_lead_forward_email({"lead_id": lead_id, "supplier_id": supplier_id})
_assert_common_design(_call_kwargs(mock_send_email)["html"])
# ── Lead Matched Notification ────────────────────────────────────
@@ -362,70 +315,55 @@ class TestLeadForward:
class TestLeadMatched:
@pytest.mark.asyncio
async def test_sends_to_lead_contact_email(self, db):
async def test_sends_to_lead_contact_email(self, db, mock_send_email):
lead_id = await _seed_lead(db)
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_lead_matched_notification({"lead_id": lead_id})
assert _call_kwargs(mock_send)["to"] == "lead@buyer.com"
await handle_send_lead_matched_notification({"lead_id": lead_id})
assert _call_kwargs(mock_send_email)["to"] == "lead@buyer.com"
@pytest.mark.asyncio
async def test_subject_contains_first_name(self, db):
async def test_subject_contains_first_name(self, db, mock_send_email):
lead_id = await _seed_lead(db)
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_lead_matched_notification({"lead_id": lead_id})
assert "John" in _call_kwargs(mock_send)["subject"]
await handle_send_lead_matched_notification({"lead_id": lead_id})
assert "John" in _call_kwargs(mock_send_email)["subject"]
@pytest.mark.asyncio
async def test_html_contains_what_happens_next(self, db):
async def test_html_contains_what_happens_next(self, db, mock_send_email):
lead_id = await _seed_lead(db)
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_lead_matched_notification({"lead_id": lead_id})
html = _call_kwargs(mock_send)["html"]
# "What happens next" section and tip callout (blue bg)
assert "#F0F9FF" in html # tip callout background
await handle_send_lead_matched_notification({"lead_id": lead_id})
html = _call_kwargs(mock_send_email)["html"]
# "What happens next" section and tip callout (blue bg)
assert "#F0F9FF" in html # tip callout background
@pytest.mark.asyncio
async def test_html_contains_project_context(self, db):
async def test_html_contains_project_context(self, db, mock_send_email):
lead_id = await _seed_lead(db)
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_lead_matched_notification({"lead_id": lead_id})
html = _call_kwargs(mock_send)["html"]
assert "Indoor" in html
assert "Germany" in html
await handle_send_lead_matched_notification({"lead_id": lead_id})
html = _call_kwargs(mock_send_email)["html"]
assert "Indoor" in html
assert "Germany" in html
@pytest.mark.asyncio
async def test_uses_leads_from_addr(self, db):
async def test_uses_leads_from_addr(self, db, mock_send_email):
lead_id = await _seed_lead(db)
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_lead_matched_notification({"lead_id": lead_id})
assert _call_kwargs(mock_send)["from_addr"] == core.EMAIL_ADDRESSES["leads"]
await handle_send_lead_matched_notification({"lead_id": lead_id})
assert _call_kwargs(mock_send_email)["from_addr"] == core.EMAIL_ADDRESSES["leads"]
@pytest.mark.asyncio
async def test_skips_when_lead_not_found(self, db):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_lead_matched_notification({"lead_id": 99999})
mock_send.assert_not_called()
async def test_skips_when_lead_not_found(self, db, mock_send_email):
await handle_send_lead_matched_notification({"lead_id": 99999})
mock_send_email.assert_not_called()
@pytest.mark.asyncio
async def test_skips_when_no_contact_email(self, db):
async def test_skips_when_no_contact_email(self, db, mock_send_email):
lead_id = await _seed_lead(db, contact_email="")
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_lead_matched_notification({"lead_id": lead_id})
mock_send.assert_not_called()
await handle_send_lead_matched_notification({"lead_id": lead_id})
mock_send_email.assert_not_called()
@pytest.mark.asyncio
async def test_design_elements_present(self, db):
async def test_design_elements_present(self, db, mock_send_email):
lead_id = await _seed_lead(db)
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_lead_matched_notification({"lead_id": lead_id})
_assert_common_design(_call_kwargs(mock_send)["html"])
await handle_send_lead_matched_notification({"lead_id": lead_id})
_assert_common_design(_call_kwargs(mock_send_email)["html"])
# ── Supplier Enquiry ─────────────────────────────────────────────
@@ -441,50 +379,43 @@ class TestSupplierEnquiry:
}
@pytest.mark.asyncio
async def test_sends_to_supplier_email(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_supplier_enquiry_email(self._BASE_PAYLOAD)
assert _call_kwargs(mock_send)["to"] == "supplier@corp.com"
async def test_sends_to_supplier_email(self, mock_send_email):
await handle_send_supplier_enquiry_email(self._BASE_PAYLOAD)
assert _call_kwargs(mock_send_email)["to"] == "supplier@corp.com"
@pytest.mark.asyncio
async def test_subject_contains_contact_name(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_supplier_enquiry_email(self._BASE_PAYLOAD)
assert "Alice Smith" in _call_kwargs(mock_send)["subject"]
async def test_subject_contains_contact_name(self, mock_send_email):
await handle_send_supplier_enquiry_email(self._BASE_PAYLOAD)
assert "Alice Smith" in _call_kwargs(mock_send_email)["subject"]
@pytest.mark.asyncio
async def test_html_contains_message(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_supplier_enquiry_email(self._BASE_PAYLOAD)
html = _call_kwargs(mock_send)["html"]
assert "4 courts" in html
assert "alice@buyer.com" in html
async def test_html_contains_message(self, mock_send_email):
await handle_send_supplier_enquiry_email(self._BASE_PAYLOAD)
html = _call_kwargs(mock_send_email)["html"]
assert "4 courts" in html
assert "alice@buyer.com" in html
@pytest.mark.asyncio
async def test_html_contains_respond_fast_nudge(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_supplier_enquiry_email(self._BASE_PAYLOAD)
html = _call_kwargs(mock_send)["html"]
# The respond-fast nudge line should be present
assert "24" in html # "24 hours" reference
async def test_html_contains_respond_fast_nudge(self, mock_send_email):
await handle_send_supplier_enquiry_email(self._BASE_PAYLOAD)
html = _call_kwargs(mock_send_email)["html"]
# The respond-fast nudge line should be present
assert "24" in html # "24 hours" reference
@pytest.mark.asyncio
async def test_skips_when_no_supplier_email(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_supplier_enquiry_email({**self._BASE_PAYLOAD, "supplier_email": ""})
mock_send.assert_not_called()
async def test_skips_when_no_supplier_email(self, mock_send_email):
await handle_send_supplier_enquiry_email({**self._BASE_PAYLOAD, "supplier_email": ""})
mock_send_email.assert_not_called()
@pytest.mark.asyncio
async def test_uses_transactional_from_addr(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_supplier_enquiry_email(self._BASE_PAYLOAD)
assert _call_kwargs(mock_send)["from_addr"] == core.EMAIL_ADDRESSES["transactional"]
async def test_uses_transactional_from_addr(self, mock_send_email):
await handle_send_supplier_enquiry_email(self._BASE_PAYLOAD)
assert _call_kwargs(mock_send_email)["from_addr"] == core.EMAIL_ADDRESSES["transactional"]
@pytest.mark.asyncio
async def test_design_elements_present(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_supplier_enquiry_email(self._BASE_PAYLOAD)
_assert_common_design(_call_kwargs(mock_send)["html"])
async def test_design_elements_present(self, mock_send_email):
await handle_send_supplier_enquiry_email(self._BASE_PAYLOAD)
_assert_common_design(_call_kwargs(mock_send_email)["html"])
# ── Waitlist (supplement existing test_waitlist.py) ──────────────
@@ -494,33 +425,29 @@ class TestWaitlistEmails:
"""Verify design & content for waitlist confirmation emails."""
@pytest.mark.asyncio
async def test_general_waitlist_has_preheader(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_waitlist_confirmation({"email": "u@example.com", "intent": "signup"})
html = _call_kwargs(mock_send)["html"]
assert "display:none" in html # preheader span
async def test_general_waitlist_has_preheader(self, mock_send_email):
await handle_send_waitlist_confirmation({"email": "u@example.com", "intent": "signup"})
html = _call_kwargs(mock_send_email)["html"]
assert "display:none" in html # preheader span
@pytest.mark.asyncio
async def test_supplier_waitlist_mentions_plan(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_waitlist_confirmation({"email": "s@example.com", "intent": "supplier_growth"})
kw = _call_kwargs(mock_send)
assert "growth" in kw["subject"].lower()
assert "supplier" in kw["html"].lower()
async def test_supplier_waitlist_mentions_plan(self, mock_send_email):
await handle_send_waitlist_confirmation({"email": "s@example.com", "intent": "supplier_growth"})
kw = _call_kwargs(mock_send_email)
assert "growth" in kw["subject"].lower()
assert "supplier" in kw["html"].lower()
@pytest.mark.asyncio
async def test_general_waitlist_design_elements(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_waitlist_confirmation({"email": "u@example.com", "intent": "signup"})
_assert_common_design(_call_kwargs(mock_send)["html"])
async def test_general_waitlist_design_elements(self, mock_send_email):
await handle_send_waitlist_confirmation({"email": "u@example.com", "intent": "signup"})
_assert_common_design(_call_kwargs(mock_send_email)["html"])
@pytest.mark.asyncio
async def test_supplier_waitlist_perks_listed(self):
with patch("padelnomics.worker.send_email", new_callable=AsyncMock) as mock_send:
await handle_send_waitlist_confirmation({"email": "s@example.com", "intent": "supplier_pro"})
html = _call_kwargs(mock_send)["html"]
# Should have <li> perks
assert html.count("<li>") >= 3
async def test_supplier_waitlist_perks_listed(self, mock_send_email):
await handle_send_waitlist_confirmation({"email": "s@example.com", "intent": "supplier_pro"})
html = _call_kwargs(mock_send_email)["html"]
# Should have <li> perks
assert html.count("<li>") >= 3
# ── DB seed helpers ──────────────────────────────────────────────