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

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 

8 

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 

15 

16 

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 

24 

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 

31 

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 

36 

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 

41 

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 

47 

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 

56 

57 

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 

66 

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 

75 

76 quote_id = self.quote_db.add_quote(quote) 

77 

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] 

83 

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) 

88 

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"] 

99 

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) 

104 

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 

126 

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 

131 

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()) 

136 

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) 

142 

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) 

146 

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) 

151 

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() 

155 

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 

165 

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()) 

175 

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) 

180 

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"] 

205 

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 

214 

215 self.db_sql.add_quote_sql = MagicMock() # Мокаем метод, чтобы проверить вызовы 

216 

217 self.db_sql.get_some_quotes("fake_url") 

218 

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")) 

222 

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"] 

234 

235 def test_delete_all(self): 

236 self.db_sql.delete_all() 

237 self.mock_db.delete_all.assert_called_once() 

238 

239def test_empty(quote_db): 

240 assert quote_db.count() == 0 

241