Coverage for tests/test_api.py: 100%
156 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-28 09:38 +0200
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-28 09:38 +0200
1from pathlib import Path
2from tempfile import TemporaryDirectory
3from unittest.mock import MagicMock, patch
4import art_studio_tz
5import pytest
6from datetime import datetime, timezone
7from art_studio_tz import Quote, QuoteDB, MissingText, InvalidQuoteId, QuoteDBsql
9@pytest.fixture()
10def quote_db():
11 with TemporaryDirectory() as db_dir:
12 db_path = Path(db_dir)
13 db = art_studio_tz.QuoteDB(db_path)
14 yield db
17class TestQuote:
18 def test_quote_creation(self):
19 q = Quote(text="Hello world", author="Max", timestep="2025-09-27")
20 assert q.text == "Hello world"
21 assert q.author == "Max"
22 assert q.timestep == "2025-09-27"
23 assert q.id is None
25 def test_quote_default_values(self):
26 q = Quote()
27 assert q.text is None
28 assert q.author is None
29 assert q.timestep is None
30 assert q.id is None
32 def test_quote_comparison_ignores_id(self):
33 q1 = Quote(text="Hello", author="Alice", timestep="t1", id=1)
34 q2 = Quote(text="Hello", author="Alice", timestep="t1", id=2)
35 assert q1 == q2 # сравнение игнорирует id
37 def test_quote_different_objects(self):
38 q1 = Quote(text="Hello", author="Alice")
39 q2 = Quote(text="Hello", author="Bob")
40 assert q1 != q2
42 def test_to_dict(self):
43 q = Quote(text="Hello", author="Alice", timestep="t1", id=10)
44 d = q.to_dict()
45 expected = {"text": "Hello", "author": "Alice", "timestep": "t1", "id": 10}
46 assert d == expected
48 def test_from_dict(self):
49 data = {"text": "Hello", "author": "Alice", "timestep": "t1", "id": 10}
50 q = Quote.from_dict(data)
51 assert isinstance(q, Quote)
52 assert q.text == "Hello"
53 assert q.author == "Alice"
54 assert q.timestep == "t1"
55 assert q.id == 10
58class TestQuoteDB:
59 @pytest.fixture(autouse=True)
60 def setup(self):
61 with patch("art_studio_tz.api.DB") as MockDB:
62 self.mock_db = MagicMock()
63 MockDB.return_value = self.mock_db
64 self.quote_db = QuoteDB(Path("fake_path"))
65 yield
67 @pytest.mark.parametrize("text,author", [
68 ("Hello", "Someone"),
69 ("Another quote", "Author"),
70 ("Quote without author", None),
71 ])
72 def test_add_quote_returns_id(self, text, author):
73 quote = Quote(text=text, author=author)
74 self.mock_db.create.return_value = 1
76 quote_id = self.quote_db.add_quote(quote)
78 self.mock_db.create.assert_called_once_with(quote.to_dict())
79 assert quote_id == 1
80 args, kwargs = self.mock_db.update.call_args
81 assert args[0] == 1
82 assert "timestep" in args[1]
84 def test_add_quote_missing_text_raises(self):
85 quote = Quote(text=None, author="Someone")
86 with pytest.raises(MissingText):
87 self.quote_db.add_quote(quote)
89 @pytest.mark.parametrize("data", [
90 {"text": "Hello", "author": "Someone", "id": 1},
91 {"text": "Test", "author": "Alice", "id": 2},
92 ])
93 def test_get_quote_returns_quote(self, data):
94 self.mock_db.read.return_value = data
95 quote = self.quote_db.get_quote(data["id"])
96 assert isinstance(quote, Quote)
97 assert quote.text == data["text"]
98 assert quote.author == data["author"]
100 def test_get_quote_invalid_id_raises(self):
101 self.mock_db.read.return_value = None
102 with pytest.raises(InvalidQuoteId):
103 self.quote_db.get_quote(999)
105 @pytest.mark.parametrize("all_quotes,author_filter,expected_count", [
106 (
107 [{"text": "A", "author": "X", "id": 1},
108 {"text": "B", "author": "Y", "id": 2}],
109 "X", 1
110 ),
111 (
112 [{"text": "A", "author": "X", "id": 1},
113 {"text": "B", "author": "Y", "id": 2}],
114 "Y", 1
115 ),
116 (
117 [{"text": "A", "author": "X", "id": 1},
118 {"text": "B", "author": "Y", "id": 2}],
119 None, 2
120 ),
121 ])
122 def test_list_quote_filters_by_author(self, all_quotes, author_filter, expected_count):
123 self.mock_db.read_all.return_value = all_quotes
124 quotes = self.quote_db.list_quote(author=author_filter)
125 assert len(quotes) == expected_count
127 @pytest.mark.parametrize("count_val", [0, 1, 5, 42])
128 def test_count_calls_db_count(self, count_val):
129 self.mock_db.count.return_value = count_val
130 assert self.quote_db.count() == count_val
132 def test_update_quote_calls_db_update(self):
133 quote_mod = Quote(text="New text", author="New author")
134 self.quote_db.update_quote(1, quote_mod)
135 self.mock_db.update.assert_called_once_with(1, quote_mod.to_dict())
137 def test_update_quote_invalid_id_raises(self):
138 quote_mod = Quote(text="New text", author="New author")
139 self.mock_db.update.side_effect = KeyError
140 with pytest.raises(InvalidQuoteId):
141 self.quote_db.update_quote(999, quote_mod)
143 def test_delete_quote_calls_db_delete(self):
144 self.quote_db.delete_quote(1)
145 self.mock_db.delete.assert_called_once_with(1)
147 def test_delete_quote_invalid_id_raises(self):
148 self.mock_db.delete.side_effect = KeyError
149 with pytest.raises(InvalidQuoteId):
150 self.quote_db.delete_quote(999)
152 def test_delete_all_calls_db_delete_all(self):
153 self.quote_db.delete_all()
154 self.mock_db.delete_all.assert_called_once()
156class TestQuoteDBsql:
157 @pytest.fixture(autouse=True)
158 def setup(self):
159 # Патчим DBsql там, где QuoteDBsql его реально использует
160 with patch("art_studio_tz.api.DBsql") as MockDBsql:
161 self.mock_db = MagicMock()
162 MockDBsql.return_value = self.mock_db
163 self.db_sql = QuoteDBsql("user", "pass")
164 yield
166 @pytest.mark.parametrize("text,author", [
167 ("Hello", "Someone"),
168 ("Another quote", "Author"),
169 ("Quote without author", None),
170 ])
171 def test_add_quote_sql(self, text, author):
172 quote = Quote(text=text, author=author)
173 self.db_sql.add_quote_sql(quote)
174 self.mock_db.create.assert_called_once_with(quote.to_dict())
176 def test_add_quote_sql_missing_text_raises(self):
177 quote = Quote(text=None, author="Someone")
178 with pytest.raises(MissingText):
179 self.db_sql.add_quote_sql(quote)
181 @pytest.mark.parametrize("all_quotes,author_filter,expected_count", [
182 (
183 [{"text": "A", "author": "X", "id": 1},
184 {"text": "B", "author": "Y", "id": 2}],
185 "X", 1
186 ),
187 (
188 [{"text": "A", "author": "X", "id": 1},
189 {"text": "B", "author": "Y", "id": 2}],
190 "Y", 1
191 ),
192 (
193 [{"text": "A", "author": "X", "id": 1},
194 {"text": "B", "author": "Y", "id": 2}],
195 None, 2
196 ),
197 ])
198 def test_list_quote(self, all_quotes, author_filter, expected_count):
199 self.mock_db.read_all.return_value = all_quotes
200 quotes = self.db_sql.list_quote(author=author_filter)
201 assert len(quotes) == expected_count
202 for q, data in zip(quotes, [t for t in all_quotes if author_filter is None or t["author"] == author_filter]):
203 assert q.text == data["text"]
204 assert q.author == data["author"]
206 @patch("art_studio_tz.api.requests.get")
207 @patch("time.sleep", return_value=None) # чтобы не ждать паузы
208 def test_get_some_quotes(self, mock_sleep, mock_get):
209 mock_get.return_value.json.return_value = [
210 {"q": "Quote1", "a": "Author1"},
211 {"q": "Quote2", "a": "Author2"},
212 ]
213 mock_get.return_value.raise_for_status = lambda: None
215 self.db_sql.add_quote_sql = MagicMock() # Мокаем метод, чтобы проверить вызовы
217 self.db_sql.get_some_quotes("fake_url")
219 assert self.db_sql.add_quote_sql.call_count == 2
220 self.db_sql.add_quote_sql.assert_any_call(Quote(text="Quote1", author="Author1"))
221 self.db_sql.add_quote_sql.assert_any_call(Quote(text="Quote2", author="Author2"))
223 @pytest.mark.parametrize("number,return_data", [
224 (1, [{"text": "A", "author": "X"}]),
225 (3, [{"text": "A", "author": "X"}, {"text": "B", "author": "Y"}, {"text": "C", "author": "Z"}]),
226 ])
227 def test_get_latest(self, number, return_data):
228 self.mock_db.get_latest.return_value = return_data
229 quotes = self.db_sql.get_latest(number)
230 assert len(quotes) == len(return_data)
231 for q, data in zip(quotes, return_data):
232 assert q.text == data["text"]
233 assert q.author == data["author"]
235 def test_delete_all(self):
236 self.db_sql.delete_all()
237 self.mock_db.delete_all.assert_called_once()
239def test_empty(quote_db):
240 assert quote_db.count() == 0