"""Dashboard stats — pure-helper and RQ Score client contract tests.""" import asyncio import httpx from app.engine import stats as S from app.engine.stats import clients from app.engine.normalize import _posted_date from app.db.repo import _activity_score def test_posted_date_per_board(): assert _posted_date({"createdDate": "2026-06-18T10:37:17Z"}) == "2026-06-18T10:37:17Z" # Naukri assert _posted_date({"postedDate": "2026-06-18T12:46:39Z"}) == "2026-06-18T12:46:39Z" # LinkedIn assert _posted_date({"date_posted": "2026-06-17T17:38:14Z"}) == "2026-06-17T17:38:14Z" # Foundit assert _posted_date({"jobDetails": {"createdDate": "2026-06-18T00:00:00Z"}}) is not None # nested assert _posted_date({"nope": "x"}) is None def test_match_scores_and_histogram(): opps = [{"match": {"score": 84}}, {"matchScore": 72}, {"match": {"score": 0}}, {}] scores = S._match_scores(opps) assert sorted(scores) == [72, 84] # 0 and missing dropped h = S._histogram(scores) assert sum(h) == 2 and len(h) == 11 def test_scout_stats_accepts_numeric_applicant_strings(monkeypatch): feed = {"opportunities": [{"applicants": "10"}, {"applicants": 30}, {"applicants": "unknown"}]} activity = { "matches": 3, "viewed": 0, "saved": 0, "applied": 0, "tailored": 0, "searches": 1, "percentile": None, "cohort_size": 1, } monkeypatch.setattr(S._repo, "get_feed", lambda _user_id: asyncio.sleep(0, result=feed)) monkeypatch.setattr(S._repo, "get_activity_stats", lambda _user_id: asyncio.sleep(0, result=activity)) monkeypatch.setattr(S._repo, "get_active_window", lambda _user_id: asyncio.sleep(0, result={})) monkeypatch.setattr(S._repo, "get_salary_band", lambda _user_id: asyncio.sleep(0, result=None)) monkeypatch.setattr(S._clients, "fetch_qx", lambda _user_uuid: asyncio.sleep(0, result=None)) monkeypatch.setattr(S._clients, "fetch_qx_trend", lambda _user_uuid: asyncio.sleep(0, result=None)) monkeypatch.setattr(S._clients, "fetch_streak", lambda _user_id: asyncio.sleep(0, result=None)) result = asyncio.run(S.build_scout_stats("user-1")) assert result["avgApplicants"] == 20 def test_apply_window_freshness(): # all None when no posting dates (honest — never fabricated) assert S._apply_window([{}, {"posted_date": None}])["urgency"] is None w = S._apply_window([{"posted_date": "2026-06-19T00:00:00Z"}, {"posted_date": "2026-06-15T00:00:00Z"}]) assert w["dated"] == 2 and w["median_age_days"] is not None and w["urgency"] in ("high", "medium", "low") def test_activity_score_weighting(): # applies weighted highest, then saves, views, searches assert _activity_score(0, 0, 1, 0) > _activity_score(0, 1, 0, 0) > _activity_score(1, 0, 0, 0) assert _activity_score(0, 0, 0, 0) == 0 class _Response: status_code = 200 def __init__(self, payload): self._payload = payload def raise_for_status(self): return None def json(self): return self._payload def test_fetch_qx_reads_strict_rq_score_contract(monkeypatch): async def _get(_self, _url, **_kwargs): return _Response({"rq_score": 68.25, "quotients": {"technical": 71}}) monkeypatch.setattr(httpx.AsyncClient, "get", _get) assert asyncio.run(clients.fetch_qx("user-uuid")) == { "qx": 68.2, "quotients": {"technical": 71}, } def test_fetch_qx_does_not_alias_legacy_q_score(monkeypatch): async def _get(_self, _url, **_kwargs): return _Response({"q_score": 99, "quotients": {"technical": 99}}) monkeypatch.setattr(httpx.AsyncClient, "get", _get) assert asyncio.run(clients.fetch_qx("user-uuid")) is None def test_fetch_qx_treats_malformed_payload_as_absent(monkeypatch): async def _get(_self, _url, **_kwargs): return _Response([{"rq_score": 88}]) monkeypatch.setattr(httpx.AsyncClient, "get", _get) assert asyncio.run(clients.fetch_qx("user-uuid")) is None def test_fetch_qx_trend_ignores_malformed_and_absent_points(monkeypatch): async def _get(_self, _url, **_kwargs): return _Response({"points": [None, {"rq_score": "bad"}, {"rq_score": 35}, {"rq_score": 40}]}) monkeypatch.setattr(httpx.AsyncClient, "get", _get) assert asyncio.run(clients.fetch_qx_trend("user-uuid")) == [35.0, 40.0] def test_fetch_qx_trend_reads_strict_rq_score_points(monkeypatch): async def _get(_self, _url, **_kwargs): return _Response({ "points": [ {"rq_score": 35, "q_score": 91}, {"rq_score": 44.16, "q_score": 92}, {"q_score": 93}, ] }) monkeypatch.setattr(httpx.AsyncClient, "get", _get) assert asyncio.run(clients.fetch_qx_trend("user-uuid")) == [35.0, 44.2]