from datetime import datetime, timedelta, timezone from services.pchome_sales_source_reconciliation import build_source_reconciliation NOW = datetime(2026, 7, 14, 18, 30, tzinfo=timezone(timedelta(hours=8))) def _source(source_id, *, enabled=True, ready=True, status="ready"): return { "id": source_id, "enabled": enabled, "ready": ready, "status": status, } def _receipt(provider, *, completed_at, reconciliation=None): stages = {"providers": [provider]} if reconciliation: stages["source_reconciliation"] = reconciliation return { "status": "blocked_with_safe_next_action", "completed_at": completed_at, "stages": stages, } def test_stale_single_source_schedules_retry_and_reports_fallback_gap(): payload = build_source_reconciliation( sources=[ _source("google_drive"), _source("authorized_https", enabled=False, ready=False, status="disabled"), ], provider_attempts=[ {"source": "google_drive", "status": "upstream_stale", "file_count": 0}, {"source": "authorized_https", "status": "disabled"}, ], prior_receipts=[], freshness={"latest_sales_date": "2026-06-24", "data_lag_days": 20, "decision_ready": False}, now=NOW, ) assert payload["contract"] == "pchome_sales_source_reconciliation_v1" assert payload["status"] == "degraded" assert payload["fallback_chain"] == ["google_drive"] assert payload["single_source_dependency"] is True assert payload["next_retry_at"] == "2026-07-14T19:00:00+08:00" assert payload["safe_next_action"] == "scheduled_retry_and_enabled_fallback_probe" assert payload["notification_policy"]["severity"] == "critical" assert payload["notification_policy"]["send"] is True assert payload["sources"][0]["consecutive_empty_runs"] == 1 assert payload["sources"][1]["runtime_state"] == "disabled" def test_same_state_within_cooldown_suppresses_duplicate_notification(): first = build_source_reconciliation( sources=[_source("google_drive")], provider_attempts=[{"source": "google_drive", "status": "upstream_stale"}], prior_receipts=[], freshness={"data_lag_days": 20, "decision_ready": False}, now=NOW - timedelta(minutes=10), ) history = [_receipt( {"source": "google_drive", "status": "upstream_stale"}, completed_at=(NOW - timedelta(minutes=10)).isoformat(), reconciliation=first, )] second = build_source_reconciliation( sources=[_source("google_drive")], provider_attempts=[{"source": "google_drive", "status": "upstream_stale"}], prior_receipts=history, freshness={"data_lag_days": 20, "decision_ready": False}, now=NOW, ) assert second["notification_policy"]["fingerprint"] == first["notification_policy"]["fingerprint"] assert second["notification_policy"]["send"] is False assert second["notification_policy"]["reason"] == "duplicate_within_cooldown" assert second["sources"][0]["consecutive_empty_runs"] == 2 def test_provider_failures_use_bounded_exponential_backoff(): failed = {"source": "authorized_https", "status": "failed", "error_kind": "http_fetch_failed"} history = [ _receipt(failed, completed_at=(NOW - timedelta(minutes=30)).isoformat()), _receipt(failed, completed_at=(NOW - timedelta(minutes=60)).isoformat()), ] payload = build_source_reconciliation( sources=[_source("authorized_https")], provider_attempts=[failed], prior_receipts=history, freshness={"data_lag_days": 5, "decision_ready": False}, now=NOW, retry_interval_minutes=30, max_failure_backoff_minutes=240, ) source = payload["sources"][0] assert source["runtime_state"] == "failed" assert source["consecutive_failure_runs"] == 3 assert source["retry_minutes"] == 120 assert source["next_retry_at"] == "2026-07-14T20:30:00+08:00" assert source["action"] == "retry_with_bounded_backoff_and_probe_fallback" def test_duplicate_source_remains_on_scheduled_retry_without_writing(): payload = build_source_reconciliation( sources=[_source("google_drive")], provider_attempts=[{ "source": "google_drive", "status": "duplicate", "duplicate_count": 1, }], prior_receipts=[], freshness={"data_lag_days": 10, "decision_ready": False}, now=NOW, ) source = payload["sources"][0] assert source["runtime_state"] == "duplicate" assert source["retry_minutes"] == 30 assert source["next_retry_at"] == "2026-07-14T19:00:00+08:00" assert payload["safe_next_action"] == "scheduled_retry_and_enabled_fallback_probe"