返回 last30days-skill
test_reddit_arctic.py
根目录 / tests / test_reddit_arctic.py
1 """Tests for reddit_arctic — keyless post-score lookup via arctic-shift (U6)."""
2
3 from unittest import mock
4
5 import pytest
6
7 from lib import reddit_arctic
8
9
10 @pytest.fixture(autouse=True)
11 def _no_arctic_network():
12 # Override conftest's stub so this module exercises the real fetch_scores;
13 # network is mocked per-test at http.get. Clear the in-run cache each time.
14 reddit_arctic._cache.clear()
15 yield
16 reddit_arctic._cache.clear()
17
18
19 def _resp(rows):
20 return {"data": rows}
21
22
23 class TestFetchScores:
24 def test_returns_scores_by_id(self):
25 with mock.patch.object(reddit_arctic.http, "get",
26 return_value=_resp([{"id": "abc", "score": 1531, "num_comments": 336}])):
27 out = reddit_arctic.fetch_scores(["abc"])
28 assert out == {"abc": {"score": 1531, "num_comments": 336}}
29
30 def test_strips_t3_prefix(self):
31 with mock.patch.object(reddit_arctic.http, "get",
32 return_value=_resp([{"id": "t3_xyz", "score": 5, "num_comments": 2}])):
33 out = reddit_arctic.fetch_scores(["xyz"])
34 assert out["xyz"]["score"] == 5
35
36 def test_rate_limit_response_degrades(self):
37 # arctic-shift answers {"error": "...slow down"} with no data list.
38 with mock.patch.object(reddit_arctic.http, "get",
39 return_value={"error": "Timeout. Maybe slow down a bit"}):
40 out = reddit_arctic.fetch_scores(["abc"])
41 assert out == {}
42
43 def test_network_error_degrades(self):
44 with mock.patch.object(reddit_arctic.http, "get", side_effect=Exception("boom")):
45 out = reddit_arctic.fetch_scores(["abc"])
46 assert out == {}
47
48 def test_in_run_cache_avoids_refetch(self):
49 with mock.patch.object(reddit_arctic.http, "get",
50 return_value=_resp([{"id": "abc", "score": 9, "num_comments": 1}])) as g:
51 reddit_arctic.fetch_scores(["abc"])
52 reddit_arctic.fetch_scores(["abc"]) # served from cache, no 2nd call
53 assert g.call_count == 1
54
55 def test_dedupes_into_single_batch(self):
56 with mock.patch.object(reddit_arctic.http, "get",
57 return_value=_resp([{"id": "a", "score": 1, "num_comments": 0},
58 {"id": "b", "score": 2, "num_comments": 0}])) as g:
59 out = reddit_arctic.fetch_scores(["a", "b", "a", ""])
60 assert g.call_count == 1
61 assert set(out) == {"a", "b"}
62
63 def test_cache_is_size_bounded(self):
64 # The in-run memo never grows past CACHE_MAX; scores are still returned
65 # for the current call once the cache is full.
66 with mock.patch.object(reddit_arctic, "CACHE_MAX", 1), \
67 mock.patch.object(reddit_arctic.http, "get",
68 return_value=_resp([{"id": "a", "score": 1, "num_comments": 0},
69 {"id": "b", "score": 2, "num_comments": 0}])):
70 out = reddit_arctic.fetch_scores(["a", "b"])
71 assert set(out) == {"a", "b"} # both returned
72 assert len(reddit_arctic._cache) <= 1 # cache stayed bounded
73
74 def test_empty_input_makes_no_call(self):
75 with mock.patch.object(reddit_arctic.http, "get") as g:
76 out = reddit_arctic.fetch_scores([])
77 assert out == {}
78 g.assert_not_called()
79
80 def test_malformed_rows_skipped(self):
81 rows = [{"id": "ok", "score": 7, "num_comments": 3},
82 {"id": "", "score": 1}, # no id
83 "not-a-dict", # junk
84 {"id": "bad", "score": "x"}] # unparseable score
85 with mock.patch.object(reddit_arctic.http, "get", return_value=_resp(rows)):
86 out = reddit_arctic.fetch_scores(["ok", "bad"])
87 assert out == {"ok": {"score": 7, "num_comments": 3}}
88
88 lines PYTHON