Skip to content

Commit c261caa

Browse files
EstrellaXDclaude
andcommitted
test(parser): add regression tests for issue #990
10 tests covering the full bug chain: - raw_parser misparses leading number as episode - TitleParser.raw_parser returns None for unparseable titles - add_title_alias rejects None and empty string - _get_aliases_list filters null values from JSON - get_all_title_patterns skips None title_raw - match_torrent and match_list handle corrupted data Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c7206a3 commit c261caa

1 file changed

Lines changed: 171 additions & 0 deletions

File tree

backend/src/test/test_issue_bugs.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,174 @@ def test_engine_caches_filter_pattern(self):
288288
p1 = engine._get_filter_pattern("720,1080")
289289
p2 = engine._get_filter_pattern("720,1080")
290290
assert p1 is p2
291+
292+
293+
# ---------------------------------------------------------------------------
294+
# Issue #990: Titles starting with numbers cause title_raw=None, crashing
295+
# the RSS loop with TypeError in match_torrent
296+
# https://github.com/EstrellaXD/Auto_Bangumi/issues/990
297+
#
298+
# "[ANi] 29 岁单身中坚冒险家的日常 - 07" → regex matches "29 " as episode,
299+
# title becomes empty → title_raw=None → None stored as alias → crash on
300+
# `None in torrent_name` in match_torrent.
301+
# ---------------------------------------------------------------------------
302+
303+
304+
class TestIssue990NumberPrefixTitle:
305+
"""Issue #990: Titles starting with numbers crash RSS loop."""
306+
307+
PROBLEM_TITLE = "[ANi] 29 岁单身中坚冒险家的日常 - 07 [1080P][Baha][WEB-DL][AAC AVC][CHT][MP4]"
308+
309+
def test_raw_parser_misparses_leading_number_as_episode(self):
310+
"""raw_parser matches leading '29 ' as episode number, losing the title."""
311+
result = raw_parser(self.PROBLEM_TITLE)
312+
# The regex matches "29 " as the episode pattern, so episode=29
313+
# and all title fields are None
314+
assert result is not None
315+
assert result.episode == 29
316+
assert result.title_en is None
317+
assert result.title_zh is None
318+
assert result.title_jp is None
319+
320+
def test_title_parser_returns_none_when_title_raw_empty(self):
321+
"""TitleParser.raw_parser returns None when no title can be extracted."""
322+
from module.parser.title_parser import TitleParser
323+
324+
result = TitleParser.raw_parser(self.PROBLEM_TITLE)
325+
# Should return None instead of a Bangumi with title_raw=None
326+
assert result is None
327+
328+
def test_add_title_alias_rejects_none(self, db_session):
329+
"""add_title_alias should reject None as alias."""
330+
from module.database.bangumi import BangumiDatabase
331+
from module.models import Bangumi
332+
333+
db = BangumiDatabase(db_session)
334+
bangumi = Bangumi(
335+
official_title="29岁单身冒险家的日常",
336+
title_raw="[ANi] 29岁单身冒险家的日常",
337+
season=1,
338+
)
339+
db_session.add(bangumi)
340+
db_session.commit()
341+
342+
result = db.add_title_alias(bangumi.id, None)
343+
assert result is False
344+
# Verify no alias was stored
345+
assert bangumi.title_aliases is None
346+
347+
def test_add_title_alias_rejects_empty_string(self, db_session):
348+
"""add_title_alias should reject empty string as alias."""
349+
from module.database.bangumi import BangumiDatabase
350+
from module.models import Bangumi
351+
352+
db = BangumiDatabase(db_session)
353+
bangumi = Bangumi(
354+
official_title="Test Anime",
355+
title_raw="[Group] Test Anime",
356+
season=1,
357+
)
358+
db_session.add(bangumi)
359+
db_session.commit()
360+
361+
result = db.add_title_alias(bangumi.id, "")
362+
assert result is False
363+
364+
def test_get_aliases_list_filters_null_values(self):
365+
"""_get_aliases_list should filter out null values from JSON."""
366+
from module.database.bangumi import _get_aliases_list
367+
from module.models import Bangumi
368+
369+
bangumi = Bangumi(title_raw="test", official_title="Test")
370+
# Simulates the corrupted state: [null] stored in DB
371+
bangumi.title_aliases = "[null]"
372+
assert _get_aliases_list(bangumi) == []
373+
374+
# Mixed valid and null values
375+
bangumi.title_aliases = '[null, "valid_alias", null, "another"]'
376+
assert _get_aliases_list(bangumi) == ["valid_alias", "another"]
377+
378+
def test_get_all_title_patterns_skips_none_title_raw(self, db_session):
379+
"""get_all_title_patterns should return empty list when title_raw is None."""
380+
from module.database.bangumi import BangumiDatabase
381+
from module.models import Bangumi
382+
383+
db = BangumiDatabase(db_session)
384+
bangumi = Bangumi(official_title="Test Anime")
385+
bangumi.title_raw = None
386+
bangumi.title_aliases = None
387+
388+
patterns = db.get_all_title_patterns(bangumi)
389+
assert patterns == []
390+
391+
def test_match_torrent_no_crash_on_none_title_raw(self, db_session):
392+
"""match_torrent should not crash when a bangumi has None title_raw."""
393+
from module.database.bangumi import BangumiDatabase
394+
from module.models import Bangumi
395+
396+
db = BangumiDatabase(db_session)
397+
# Insert a bangumi with corrupted title_raw (simulating the bug state)
398+
bangumi = Bangumi(
399+
official_title="29岁单身冒险家的日常",
400+
season=1,
401+
)
402+
bangumi.title_raw = None
403+
db_session.add(bangumi)
404+
db_session.commit()
405+
406+
# Should not raise TypeError: 'in <string>' requires string
407+
result = db.match_torrent(
408+
"[ANi] 29 岁单身中坚冒险家的日常 - 07 [1080P][Baha][WEB-DL]"
409+
)
410+
assert result is None
411+
412+
def test_match_torrent_no_crash_on_null_aliases(self, db_session):
413+
"""match_torrent should not crash when title_aliases contains null."""
414+
from module.database.bangumi import BangumiDatabase
415+
from module.models import Bangumi
416+
417+
db = BangumiDatabase(db_session)
418+
bangumi = Bangumi(
419+
official_title="29岁单身冒险家的日常",
420+
title_raw="[ANi] 29岁单身冒险家的日常",
421+
season=1,
422+
)
423+
bangumi.title_aliases = "[null]"
424+
db_session.add(bangumi)
425+
db_session.commit()
426+
427+
# Should not crash — null aliases are filtered out
428+
result = db.match_torrent(
429+
"[ANi] 29岁单身冒险家的日常 - 07 [1080P][Baha][WEB-DL]"
430+
)
431+
assert result is not None
432+
assert result.official_title == "29岁单身冒险家的日常"
433+
434+
def test_match_list_no_crash_on_corrupted_data(self, db_session):
435+
"""match_list should handle bangumi with None title_raw and null aliases."""
436+
from module.database.bangumi import BangumiDatabase
437+
from module.models import Bangumi
438+
from unittest.mock import MagicMock
439+
440+
db = BangumiDatabase(db_session)
441+
442+
# Insert corrupted bangumi (title_raw=None, aliases=[null])
443+
bangumi = Bangumi(official_title="29岁单身冒险家的日常", season=1)
444+
bangumi.title_raw = None
445+
bangumi.title_aliases = "[null]"
446+
db_session.add(bangumi)
447+
448+
# Insert a valid bangumi
449+
valid = Bangumi(
450+
official_title="葬送的芙莉莲",
451+
title_raw="葬送的芙莉莲 / Sousou no Frieren",
452+
season=1,
453+
)
454+
db_session.add(valid)
455+
db_session.commit()
456+
457+
torrent = MagicMock()
458+
torrent.name = "[ANi] 29 岁单身中坚冒险家的日常 - 07 [1080P]"
459+
460+
# Should not crash even with corrupted data in the DB
461+
unmatched = db.match_list([torrent], "https://mikanani.me/RSS/test")

0 commit comments

Comments
 (0)