"""Tests for harness/youtube_analytics.py — pure helpers + collect/status with mocked API.""" from datetime import datetime, timedelta, timezone from harness import youtube_analytics as ya from harness.attribution import Attribution from harness.scoreboard import CHANNEL_ACTIVE, CHANNEL_TERMINATED NOW = datetime(2026, 6, 18, tzinfo=timezone.utc) def _iso(days_ago): return (NOW - timedelta(days=days_ago)).isoformat() # ── pure helpers ────────────────────────────────────────────────────────────── def test_age_filter(): assert ya.video_is_old_enough(_iso(5), NOW, 3) is True assert ya.video_is_old_enough(_iso(1), NOW, 3) is False assert ya.video_is_old_enough("garbage", NOW, 3) is False def test_vsa_proxy_bounds(): assert ya.vsa_proxy(0) == 0.0 assert ya.vsa_proxy(100) == 1.0 assert ya.vsa_proxy(50) == 0.5 assert ya.vsa_proxy(250) == 1.0 # clamped def test_metrics_row_to_dict(): headers = [{"name": "views"}, {"name": "averageViewPercentage"}, {"name": "likes"}] row = [1000, 73.5, "42"] d = ya.metrics_row_to_dict(headers, row) assert d == {"views": 1000.0, "averageViewPercentage": 73.5, "likes": 42.0} def test_build_video_analytics_joins_attribution(): attr = Attribution("v1", "variant_2", "hash12345678", "variant_1", "2026-06-10") metrics = {"averageViewPercentage": 80.0, "views": 900, "likes": 30, "shares": 4, "comments": 5, "averageViewDuration": 18.2, "subscribersGained": 2} va = ya.build_video_analytics(video_id="v1", published_at=_iso(8), metrics=metrics, attribution=attr) assert va.variant_id == "variant_2" and va.genome_hash == "hash12345678" assert va.apv == 80.0 and va.vsa == 0.8 assert va.shares == 4 and va.subscribers_gained == 2 assert len(va.upload_date) == 10 # date only # ── collect_analytics with mocked API + attribution join ───────────────────── def test_collect_analytics_filters_and_attributes(monkeypatch): uploads = [ {"video_id": "ours_old", "published_at": _iso(6)}, {"video_id": "ours_fresh", "published_at": _iso(1)}, # too fresh -> skipped {"video_id": "not_ours", "published_at": _iso(9)}, # no attribution -> skipped ] monkeypatch.setattr(ya, "_data_client", lambda credentials: object()) monkeypatch.setattr(ya, "_analytics_client", lambda credentials: object()) monkeypatch.setattr(ya, "_recent_uploads", lambda client, channel_id: uploads) monkeypatch.setattr(ya, "_query_metrics", lambda a, c, vid, now: {"averageViewPercentage": 60.0, "views": 100}) attribution = {"ours_old": Attribution("ours_old", "A", "h", "seed", "2026-06-12"), "ours_fresh": Attribution("ours_fresh", "A", "h", "seed", "2026-06-17")} out = ya.collect_analytics(credentials={}, channel_id="LAB", attribution=attribution, now=NOW, min_age_days=3) assert [v.video_id for v in out] == ["ours_old"] # fresh skipped, not_ours skipped assert out[0].variant_id == "A" def test_channel_status_active(monkeypatch): class FakeData: def channels(self): return self def list(self, **k): return self def execute(self): return {"items": [{"id": "LAB", "status": {}}]} monkeypatch.setattr(ya, "_data_client", lambda credentials: FakeData()) assert ya.channel_status(credentials={}, channel_id="LAB") == CHANNEL_ACTIVE def test_channel_status_terminated_when_missing(monkeypatch): class FakeData: def channels(self): return self def list(self, **k): return self def execute(self): return {"items": []} monkeypatch.setattr(ya, "_data_client", lambda credentials: FakeData()) assert ya.channel_status(credentials={}, channel_id="LAB") == CHANNEL_TERMINATED