from datetime import datetime from pathlib import Path from zoneinfo import ZoneInfo import pytest from django.core import mail from django.test import override_settings from apps.jobs.models import Feedback, ScoreRun from apps.jobs.services.scoring import score_and_save from apps.notifications.models import DigestOutbox from apps.notifications.services import build_digest_payload, create_daily_outbox, send_digest from apps.sources.models import EmailMessageRecord, RawDocument from apps.sources.services.email_import import ingest_email, message_identity @pytest.mark.django_db def test_email_import_is_idempotent(profile): raw = Path("fixtures/emails/sample_alert.eml").read_bytes() identity = message_identity(raw) first = ingest_email(raw) second = ingest_email(raw) assert first.pk == second.pk assert first.message_id == identity assert EmailMessageRecord.objects.count() == 1 assert RawDocument.objects.filter(kind=RawDocument.Kind.EMAIL).count() == 1 assert len(first.links) == 2 @pytest.mark.django_db def test_digest_payload_outbox_deduplication_and_send(profile, job): score = score_and_save(job, profile) score.recommendation = ScoreRun.Recommendation.STRONG score.score = 92 score.save(update_fields=["recommendation", "score"]) local_tz = ZoneInfo(profile.timezone) due = datetime(2026, 7, 20, 7, 35, tzinfo=local_tz) payload = build_digest_payload(profile, local_date=due.date()) assert payload["strong_count"] == 1 assert payload["scores"][0]["title"] == job.original_title with override_settings(DIGEST_RECIPIENT="digest@example.invalid"): first = create_daily_outbox(profile, now=due) second = create_daily_outbox(profile, now=due) assert first is not None assert second.pk == first.pk assert first.status == DigestOutbox.Status.PENDING send_digest(first) first.refresh_from_db() assert first.status == DigestOutbox.Status.SENT assert first.sent_at is not None assert len(mail.outbox) == 1 assert "Infrastructure Engineer" in mail.outbox[0].body @pytest.mark.django_db def test_digest_skips_outside_window_and_hidden_jobs(profile, job): score_and_save(job, profile) Feedback.objects.create( user=profile.user, profile=profile, job=job, action=Feedback.Action.HIDE, ) payload = build_digest_payload(profile) assert payload["scores"] == [] local_tz = ZoneInfo(profile.timezone) too_early = datetime(2026, 7, 20, 6, 0, tzinfo=local_tz) assert create_daily_outbox(profile, now=too_early) is None