"""Tests for marketing_intelligence.evasion.detection.PageSignals.""" import pytest from marketing_intelligence.evasion.detection import PageSignals _TIKTOK_URL = "https://www.tiktok.com/tag/foryou" _NORMAL_HTML = "x" * 5000 # above empty threshold def _make_signals( *, html: str = _NORMAL_HTML, requested_url: str = _TIKTOK_URL, final_url: str = _TIKTOK_URL, response_time_ms: int = 1000, items_loaded: int = 10, count_text: str | None = None, metrics: dict | None = None, ) -> PageSignals: return PageSignals.from_page( html=html, requested_url=requested_url, final_url=final_url, response_time_ms=response_time_ms, items_loaded=items_loaded, count_text=count_text, metrics=metrics, ) # ─────────────────────────── block_signal ────────────────────────────────── class TestBlockSignal: def test_none_for_normal_page(self) -> None: assert _make_signals().block_signal == "none" def test_login_redirect_on_login_url(self) -> None: signals = _make_signals(final_url="https://www.tiktok.com/login?redirect=x") assert signals.block_signal == "login_redirect" def test_login_redirect_on_loginpage(self) -> None: signals = _make_signals(final_url="https://www.tiktok.com/loginpage") assert signals.block_signal == "login_redirect" def test_captcha_in_html(self) -> None: html = _NORMAL_HTML + "captcha" signals = _make_signals(html=html) assert signals.block_signal == "captcha" def test_captcha_variant_robot_check(self) -> None: html = _NORMAL_HTML + "robot-check" signals = _make_signals(html=html) assert signals.block_signal == "captcha" def test_empty_response_on_short_html(self) -> None: signals = _make_signals(html="x" * 100) assert signals.block_signal == "empty_response" def test_soft_block_on_non_tiktok_url(self) -> None: signals = _make_signals( final_url="https://some-proxy-error-page.com/blocked", html=_NORMAL_HTML, ) assert signals.block_signal == "soft_block" def test_login_check_is_case_insensitive(self) -> None: signals = _make_signals(final_url="https://www.tiktok.com/LOGIN?x=1") assert signals.block_signal == "login_redirect" # ─────────────────────────── page_load_quality ───────────────────────────── class TestLoadQuality: def test_empty_when_zero_items(self) -> None: assert _make_signals(items_loaded=0).page_load_quality == "empty" def test_partial_when_below_threshold(self) -> None: assert _make_signals(items_loaded=3).page_load_quality == "partial" def test_full_when_at_threshold(self) -> None: assert _make_signals(items_loaded=5).page_load_quality == "full" def test_full_when_above_threshold(self) -> None: assert _make_signals(items_loaded=20).page_load_quality == "full" # ─────────────────────────── redirect_detected ───────────────────────────── class TestRedirectDetected: def test_no_redirect_when_urls_match(self) -> None: assert _make_signals().redirect_detected is False def test_redirect_when_urls_differ(self) -> None: signals = _make_signals( requested_url="https://www.tiktok.com/tag/foryou", final_url="https://www.tiktok.com/login", ) assert signals.redirect_detected is True def test_no_redirect_trailing_slash_ignored(self) -> None: signals = _make_signals( requested_url="https://www.tiktok.com/tag/foryou/", final_url="https://www.tiktok.com/tag/foryou", ) assert signals.redirect_detected is False def test_no_redirect_query_string_ignored(self) -> None: signals = _make_signals( requested_url="https://www.tiktok.com/tag/foryou?lang=en", final_url="https://www.tiktok.com/tag/foryou", ) assert signals.redirect_detected is False # ─────────────────────────── slow_response ───────────────────────────────── class TestSlowResponse: def test_not_slow_below_threshold(self) -> None: assert _make_signals(response_time_ms=5000).slow_response is False def test_slow_above_threshold(self) -> None: assert _make_signals(response_time_ms=16000).slow_response is True def test_response_time_stored(self) -> None: assert _make_signals(response_time_ms=3000).response_time_ms == 3000 # ─────────────────────────── _parse_count ────────────────────────────────── class TestParseCount: @pytest.mark.parametrize( "text,expected", [ ("942.9K", 942_900), ("1.2M", 1_200_000), ("500", 500), ("1,000", 1000), ("2B", 2_000_000_000), ("10k", 10_000), ], ) def test_parses_various_formats(self, text: str, expected: int) -> None: assert PageSignals._parse_count(text) == expected def test_returns_none_for_none(self) -> None: assert PageSignals._parse_count(None) is None def test_returns_none_for_empty_string(self) -> None: assert PageSignals._parse_count("") is None # ─────────────────────────── items_load_ratio ────────────────────────────── class TestItemsLoadRatio: def test_ratio_computed_from_count_text(self) -> None: signals = _make_signals(items_loaded=10, count_text="100 videos") assert signals.items_load_ratio == pytest.approx(0.1) def test_none_when_no_count_text(self) -> None: signals = _make_signals(items_loaded=10, count_text=None) assert signals.items_load_ratio is None def test_ratio_with_k_suffix(self) -> None: signals = _make_signals(items_loaded=5, count_text="1K videos") assert signals.items_load_ratio == pytest.approx(0.005) # ─────────────────────────── zero_metrics ────────────────────────────────── class TestZeroMetrics: def test_true_when_all_zero(self) -> None: signals = _make_signals(metrics={"views": 0, "likes": 0, "comments": 0}) assert signals.zero_metrics is True def test_false_when_views_nonzero(self) -> None: signals = _make_signals(metrics={"views": 100, "likes": 0, "comments": 0}) assert signals.zero_metrics is False def test_true_when_metrics_empty(self) -> None: signals = _make_signals(metrics={}) assert signals.zero_metrics is True def test_false_when_any_metric_nonzero(self) -> None: signals = _make_signals(metrics={"views": 0, "likes": 50, "comments": 0}) assert signals.zero_metrics is False