SilverElixir commited on
Commit
1c088fd
·
1 Parent(s): 87f3899

Merge near-duplicate tiktok helper tests into parametrized cases

Browse files
Files changed (1) hide show
  1. tests/test_bot_tiktok.py +105 -198
tests/test_bot_tiktok.py CHANGED
@@ -103,110 +103,56 @@ def test_tiktok_video_candidates_empty_when_nothing_available():
103
  assert bot._tiktok_video_candidates({}) == []
104
 
105
 
106
- def test_original_sound_label_russian():
107
- assert bot._original_sound_label("ru") == "Оригинальный звук"
108
-
109
-
110
- def test_original_sound_label_ukrainian():
111
- assert bot._original_sound_label("uk") == "Оригінальний звук"
112
-
113
-
114
- def test_original_sound_label_belarusian():
115
- assert bot._original_sound_label("be") == "Арыгінальны гук"
116
-
117
-
118
- def test_original_sound_label_english():
119
- assert bot._original_sound_label("en") == "Original sound"
120
-
121
-
122
- def test_original_sound_label_strips_region_subtag():
123
- # Telegram может прислать региональный вариант ("en-US", "pt-BR") — берём
124
- # только первичный языковой подтег до дефиса.
125
- assert bot._original_sound_label("en-US") == "Original sound"
126
- assert bot._original_sound_label("pt-BR") == "Som original"
127
-
128
-
129
- def test_original_sound_label_falls_back_to_english_for_unknown_code():
130
- assert bot._original_sound_label("sw") == "Original sound"
131
- assert bot._original_sound_label("xx-YY") == "Original sound"
132
-
133
-
134
- def test_original_sound_label_falls_back_to_english_when_missing():
135
- assert bot._original_sound_label(None) == "Original sound"
136
- assert bot._original_sound_label("") == "Original sound"
137
-
138
-
139
- def test_original_sound_label_case_insensitive():
140
- assert bot._original_sound_label("RU") == "Оригинальный звук"
141
-
142
-
143
- def test_original_sound_label_fallback_chain():
144
  # Цепочка: язык отправителя → язык чата (/lang) → английский.
145
- assert bot._original_sound_label("de", "uk") == "Originalton"
146
- assert bot._original_sound_label(None, "uk") == "Оригінальний звук"
147
- assert bot._original_sound_label("sw", "kk") == "Түпнұсқа дыбыс"
148
- assert bot._original_sound_label("th", "kk") == "เสียงต้นฉบับ"
149
- assert bot._original_sound_label("sw", "xx") == "Original sound"
150
- assert bot._original_sound_label(None, None) == "Original sound"
151
-
152
-
153
- def test_tiktok_music_page_id_extracts_trailing_numeric_id():
154
- url = "https://www.tiktok.com/music/original-sound-7666630127215823637"
155
- assert bot._tiktok_music_page_id(url) == "7666630127215823637"
156
-
157
-
158
- def test_tiktok_music_page_id_extracts_id_with_cyrillic_slug():
159
- # Реальный найденный в логах случай — слаг на русском языке.
160
- url = "https://www.tiktok.com/music/оригинальный-звук-7667114246303812385"
161
- assert bot._tiktok_music_page_id(url) == "7667114246303812385"
162
-
163
-
164
- def test_tiktok_music_page_id_handles_trailing_slash():
165
- url = "https://www.tiktok.com/music/original-sound-7666630127215823637/"
166
- assert bot._tiktok_music_page_id(url) == "7666630127215823637"
167
-
168
-
169
- def test_tiktok_music_page_id_none_for_regular_video_link():
170
- url = "https://www.tiktok.com/@someuser/video/7370000000000000001"
171
- assert bot._tiktok_music_page_id(url) is None
172
-
173
-
174
- def test_tiktok_music_page_id_none_for_photo_post_link():
175
- url = "https://www.tiktok.com/@someuser/photo/7370000000000000002"
176
- assert bot._tiktok_music_page_id(url) is None
177
-
178
-
179
- def test_tiktok_music_page_id_none_for_named_track_slug():
180
- # Именованные треки/песни тоже используют /music/, просто со своим слагом —
181
- # функция всё равно должна найти числовой ID (сама эвристика "сработает ли
182
- # скачивание" находится не здесь, а в handle_tiktok_sound).
183
- url = "https://www.tiktok.com/music/Blinding-Lights-6862178485109294850"
184
- assert bot._tiktok_music_page_id(url) == "6862178485109294850"
185
-
186
-
187
- def test_looks_like_resolved_tiktok_url_true_for_proper_video_url():
188
- assert bot._looks_like_resolved_tiktok_url("https://www.tiktok.com/@someuser/video/7370000000000000001") is True
189
-
190
-
191
- def test_looks_like_resolved_tiktok_url_true_for_proper_photo_url():
192
- assert bot._looks_like_resolved_tiktok_url("https://www.tiktok.com/@someuser/photo/7370000000000000002") is True
193
-
194
-
195
- def test_looks_like_resolved_tiktok_url_false_for_empty_username():
196
- # Точно тот URL, ч��о реально ушёл в TikWM и получил 403 в реальном инциденте.
197
- assert bot._looks_like_resolved_tiktok_url("https://www.tiktok.com/@/photo/7512093374153772309") is False
198
-
199
-
200
- def test_looks_like_resolved_tiktok_url_false_for_bare_at_sign_without_post():
201
- # Просто "@" где-то в строке (например голая страница профиля без поста, или
202
- # случайное совпадение) — раньше проходило старую слабую проверку.
203
- assert bot._looks_like_resolved_tiktok_url("https://www.tiktok.com/@someuser") is False
204
- assert bot._looks_like_resolved_tiktok_url("https://www.tiktok.com/some-page?ref=@video") is False
205
-
206
-
207
- def test_looks_like_resolved_tiktok_url_false_for_empty_or_none():
208
- assert bot._looks_like_resolved_tiktok_url("") is False
209
- assert bot._looks_like_resolved_tiktok_url(None) is False
210
 
211
 
212
  def test_resolve_tiktok_short_falls_through_to_get_when_head_gives_malformed_url():
@@ -243,99 +189,65 @@ def test_resolve_tiktok_short_returns_get_result_even_if_still_malformed():
243
  assert result == "https://www.tiktok.com/@/photo/999"
244
 
245
 
246
- def test_slideshow_slide_urls_prefers_live_images_when_present():
247
- media_data = {"live_images": ["https://tikwm.com/live0.mp4", ""]}
248
- images_to_fetch = ["https://tikwm.com/photo0.jpg", "https://tikwm.com/photo1.jpg"]
249
- assert bot._slideshow_slide_urls(media_data, images_to_fetch) == [
250
- "https://tikwm.com/live0.mp4", "https://tikwm.com/photo1.jpg",
251
- ]
252
-
253
-
254
- def test_slideshow_slide_urls_falls_back_when_live_images_absent():
255
- images_to_fetch = ["https://tikwm.com/photo0.jpg", "https://tikwm.com/photo1.jpg"]
256
- assert bot._slideshow_slide_urls({}, images_to_fetch) == images_to_fetch
257
-
258
-
259
- def test_slideshow_slide_urls_falls_back_when_live_images_shorter():
260
- media_data = {"live_images": ["https://tikwm.com/live0.mp4"]}
261
- images_to_fetch = ["https://tikwm.com/photo0.jpg", "https://tikwm.com/photo1.jpg"]
262
- assert bot._slideshow_slide_urls(media_data, images_to_fetch) == [
263
- "https://tikwm.com/live0.mp4", "https://tikwm.com/photo1.jpg",
264
- ]
265
-
266
-
267
- def test_slideshow_slide_urls_ignores_non_list_live_images():
268
- media_data = {"live_images": "not-a-list"}
269
- images_to_fetch = ["https://tikwm.com/photo0.jpg"]
270
- assert bot._slideshow_slide_urls(media_data, images_to_fetch) == images_to_fetch
271
-
272
-
273
- def test_looks_like_video_bytes_true_for_mp4_ftyp_signature():
274
  # Реальная сигнатура начала MP4/MOV-контейнера: 4 байта размера бокса + "ftyp".
275
- mp4_header = b"\x00\x00\x00\x18ftypmp42\x00\x00\x00\x00mp42isom"
276
- assert bot._looks_like_video_bytes(mp4_header) is True
277
-
278
-
279
- def test_looks_like_video_bytes_false_for_jpeg():
280
- jpeg_header = b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00"
281
- assert bot._looks_like_video_bytes(jpeg_header) is False
282
-
283
-
284
- def test_looks_like_video_bytes_false_for_png():
285
- png_header = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR"
286
- assert bot._looks_like_video_bytes(png_header) is False
287
-
288
-
289
- def test_looks_like_video_bytes_false_for_too_short_input():
290
- assert bot._looks_like_video_bytes(b"\x00\x00\x00\x18fty") is False
291
-
292
-
293
- def test_looks_like_video_bytes_false_for_empty_bytes():
294
- assert bot._looks_like_video_bytes(b"") is False
295
-
296
-
297
- def test_chunk_tiktok_media_items_exact_multiple_of_ten():
298
- items = list(range(20))
299
- chunks = bot._chunk_tiktok_media_items(items)
300
- assert [len(c) for c in chunks] == [10, 10]
301
- assert sum(chunks, []) == items
302
-
303
-
304
- def test_chunk_tiktok_media_items_avoids_trailing_single_item_at_eleven():
305
- items = list(range(11))
306
- chunks = bot._chunk_tiktok_media_items(items)
307
- assert [len(c) for c in chunks] == [9, 2]
308
- for c in chunks:
309
- assert 2 <= len(c) <= 10
310
- assert sum(chunks, []) == items
311
-
312
-
313
- def test_chunk_tiktok_media_items_avoids_trailing_single_item_at_twenty_one():
314
- items = list(range(21))
315
- chunks = bot._chunk_tiktok_media_items(items)
316
- assert [len(c) for c in chunks] == [10, 9, 2]
317
- for c in chunks:
318
- assert 2 <= len(c) <= 10
319
- assert sum(chunks, []) == items
320
-
321
-
322
- def test_chunk_tiktok_media_items_avoids_trailing_single_item_at_tiktok_max_thirty_one():
323
- items = list(range(31))
324
- chunks = bot._chunk_tiktok_media_items(items)
325
- for c in chunks:
326
- assert 2 <= len(c) <= 10
327
- assert sum(chunks, []) == items
328
-
329
-
330
- def test_chunk_tiktok_media_items_no_borrow_needed_at_thirty_five():
331
  # Официальный максимум TikTok (35) кратен 10 с остатком 5 — переноса не требуется.
332
- items = list(range(35))
 
 
 
 
333
  chunks = bot._chunk_tiktok_media_items(items)
334
- assert [len(c) for c in chunks] == [10, 10, 10, 5]
335
-
336
-
337
- def test_chunk_tiktok_media_items_empty_list():
338
- assert bot._chunk_tiktok_media_items([]) == []
 
339
 
340
 
341
  def test_chunk_tiktok_media_items_single_item_not_split_further():
@@ -1621,11 +1533,6 @@ def test_tiktok_video_candidates_protocol_relative_url():
1621
  assert candidates[0]["url"] == "https://cdn.tikwm.com/v.mp4"
1622
 
1623
 
1624
- def test_looks_like_video_bytes_true_for_webm_ebml():
1625
- # A7-9: WebM-слайд раньше принимался за фото.
1626
- assert bot._looks_like_video_bytes(b"\x1a\x45\xdf\xa3" + b"\x00" * 16) is True
1627
-
1628
-
1629
  def test_download_url_bin_honors_caller_cap():
1630
  # Аудит A7-5: видео режем лимитом отправки (50МБ), а не общим 75МБ.
1631
  mapping = {"http://8.8.8.8/v.mp4": _FakeDownloadResponse([b"123456789012345"])}
 
103
  assert bot._tiktok_video_candidates({}) == []
104
 
105
 
106
+ @pytest.mark.parametrize(("user_lang", "chat_lang", "expected"), [
107
+ ("ru", None, "Оригинальный звук"),
108
+ ("uk", None, "Оригінальний звук"),
109
+ ("be", None, "Арыгінальны гук"),
110
+ ("en", None, "Original sound"),
111
+ # Telegram присылает региональный вариант ("en-US", "pt-BR") — только первичный подтег.
112
+ ("en-US", None, "Original sound"),
113
+ ("pt-BR", None, "Som original"),
114
+ ("sw", None, "Original sound"),
115
+ ("xx-YY", None, "Original sound"),
116
+ (None, None, "Original sound"),
117
+ ("", None, "Original sound"),
118
+ ("RU", None, "Оригинальный звук"),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  # Цепочка: язык отправителя → язык чата (/lang) → английский.
120
+ ("de", "uk", "Originalton"),
121
+ (None, "uk", "Оригінальний звук"),
122
+ ("sw", "kk", "Түпнұсқа дыбыс"),
123
+ ("th", "kk", "เสียงต้นฉบับ"),
124
+ ("sw", "xx", "Original sound"),
125
+ ])
126
+ def test_original_sound_label(user_lang, chat_lang, expected):
127
+ assert bot._original_sound_label(user_lang, chat_lang) == expected
128
+
129
+
130
+ @pytest.mark.parametrize(("url", "expected"), [
131
+ ("https://www.tiktok.com/music/original-sound-7666630127215823637", "7666630127215823637"),
132
+ # Реальный случай из логов — слаг на русском языке.
133
+ ("https://www.tiktok.com/music/оригинальный-звук-7667114246303812385", "7667114246303812385"),
134
+ ("https://www.tiktok.com/music/original-sound-7666630127215823637/", "7666630127215823637"),
135
+ ("https://www.tiktok.com/@someuser/video/7370000000000000001", None),
136
+ ("https://www.tiktok.com/@someuser/photo/7370000000000000002", None),
137
+ # Именованный трек со своим слагом — числовой ID всё равно находится.
138
+ ("https://www.tiktok.com/music/Blinding-Lights-6862178485109294850", "6862178485109294850"),
139
+ ])
140
+ def test_tiktok_music_page_id(url, expected):
141
+ assert bot._tiktok_music_page_id(url) == expected
142
+
143
+
144
+ @pytest.mark.parametrize(("url", "expected"), [
145
+ ("https://www.tiktok.com/@someuser/video/7370000000000000001", True),
146
+ ("https://www.tiktok.com/@someuser/photo/7370000000000000002", True),
147
+ # Тот URL с пустым юзернеймом, что реально ушёл в TikWM и получил 403.
148
+ ("https://www.tiktok.com/@/photo/7512093374153772309", False),
149
+ ("https://www.tiktok.com/@someuser", False),
150
+ ("https://www.tiktok.com/some-page?ref=@video", False),
151
+ ("", False),
152
+ (None, False),
153
+ ])
154
+ def test_looks_like_resolved_tiktok_url(url, expected):
155
+ assert bot._looks_like_resolved_tiktok_url(url) is expected
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
 
158
  def test_resolve_tiktok_short_falls_through_to_get_when_head_gives_malformed_url():
 
189
  assert result == "https://www.tiktok.com/@/photo/999"
190
 
191
 
192
+ @pytest.mark.parametrize(("media_data", "images", "expected"), [
193
+ (
194
+ {"live_images": ["https://tikwm.com/live0.mp4", ""]},
195
+ ["https://tikwm.com/photo0.jpg", "https://tikwm.com/photo1.jpg"],
196
+ ["https://tikwm.com/live0.mp4", "https://tikwm.com/photo1.jpg"],
197
+ ),
198
+ (
199
+ {},
200
+ ["https://tikwm.com/photo0.jpg", "https://tikwm.com/photo1.jpg"],
201
+ ["https://tikwm.com/photo0.jpg", "https://tikwm.com/photo1.jpg"],
202
+ ),
203
+ (
204
+ {"live_images": ["https://tikwm.com/live0.mp4"]},
205
+ ["https://tikwm.com/photo0.jpg", "https://tikwm.com/photo1.jpg"],
206
+ ["https://tikwm.com/live0.mp4", "https://tikwm.com/photo1.jpg"],
207
+ ),
208
+ (
209
+ {"live_images": "not-a-list"},
210
+ ["https://tikwm.com/photo0.jpg"],
211
+ ["https://tikwm.com/photo0.jpg"],
212
+ ),
213
+ ])
214
+ def test_slideshow_slide_urls(media_data, images, expected):
215
+ assert bot._slideshow_slide_urls(media_data, images) == expected
216
+
217
+
218
+ @pytest.mark.parametrize(("payload", "expected"), [
 
219
  # Реальная сигнатура начала MP4/MOV-контейнера: 4 байта размера бокса + "ftyp".
220
+ (b"\x00\x00\x00\x18ftypmp42\x00\x00\x00\x00mp42isom", True),
221
+ # A7-9: WebM-слайд раньше принимался за фото.
222
+ (b"\x1a\x45\xdf\xa3" + b"\x00" * 16, True),
223
+ (b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00", False),
224
+ (b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR", False),
225
+ (b"\x00\x00\x00\x18fty", False),
226
+ (b"", False),
227
+ ])
228
+ def test_looks_like_video_bytes(payload, expected):
229
+ assert bot._looks_like_video_bytes(payload) is expected
230
+
231
+
232
+ @pytest.mark.parametrize(("n_items", "expected"), [
233
+ (20, [10, 10]),
234
+ (11, [9, 2]),
235
+ (21, [10, 9, 2]),
236
+ # Хвост-одиночка на максимуме TikTok недопустим, точный расклад не фиксируем.
237
+ (31, None),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  # Официальный максимум TikTok (35) кратен 10 с остатком 5 — переноса не требуется.
239
+ (35, [10, 10, 10, 5]),
240
+ (0, []),
241
+ ])
242
+ def test_chunk_tiktok_media_items(n_items, expected):
243
+ items = list(range(n_items))
244
  chunks = bot._chunk_tiktok_media_items(items)
245
+ if expected is not None:
246
+ assert [len(c) for c in chunks] == expected
247
+ else:
248
+ for c in chunks:
249
+ assert 2 <= len(c) <= 10
250
+ assert sum(chunks, []) == items
251
 
252
 
253
  def test_chunk_tiktok_media_items_single_item_not_split_further():
 
1533
  assert candidates[0]["url"] == "https://cdn.tikwm.com/v.mp4"
1534
 
1535
 
 
 
 
 
 
1536
  def test_download_url_bin_honors_caller_cap():
1537
  # Аудит A7-5: видео режем лимитом отправки (50МБ), а не общим 75МБ.
1538
  mapping = {"http://8.8.8.8/v.mp4": _FakeDownloadResponse([b"123456789012345"])}