feat(series): post→series flows + browse list — backend (FC-6.2)
The post-aware on-ramp + the data behind the missing Series browse view. - page_number_parser: conservative stated-page parser (pages 9-12 / page 5 / [3/8] / 3 of 8), keyword-gated to avoid false positives. Pure + unit-tested. - SeriesService.promote_post_to_series: a self-contained post becomes its own series — series tag named after the post, one chapter, the post's images as pages (ordered by capture order; stated pages parsed from title/description). - SeriesService.add_post_as_chapter: append a post as the next chapter of an existing series, titled after the post and slotted by parsed page number (a "pages 1-4" post lands ahead of the "pages 9-12" chapter). - SeriesService.list_series: browse cards — cover thumb, artist, chapter/page counts, gap flag, last-updated; sort recent|name|size + filter by artist. - API: GET /api/series, POST /api/series/from-post, POST /api/series/<id>/add-post. - Resolver uses ImageRecord.primary_post_id (same linkage the posts feed renders). Frontend (Add-to-series control + Series view + nav) lands next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
"""Pure unit tests for the stated-page parser (FC-6.2)."""
|
||||
|
||||
from backend.app.services.page_number_parser import parse_page_range
|
||||
|
||||
|
||||
def test_explicit_ranges():
|
||||
assert parse_page_range("pages 9-12") == (9, 12)
|
||||
assert parse_page_range("pg 9–12") == (9, 12) # en-dash
|
||||
assert parse_page_range("pp. 9 - 12") == (9, 12)
|
||||
assert parse_page_range("Chapter 2 (pages 1-4)") == (1, 4)
|
||||
|
||||
|
||||
def test_reversed_range_is_normalized():
|
||||
assert parse_page_range("pages 12-9") == (9, 12)
|
||||
|
||||
|
||||
def test_single_page():
|
||||
assert parse_page_range("Page 5") == (5, 5)
|
||||
assert parse_page_range("pg 5") == (5, 5)
|
||||
|
||||
|
||||
def test_fraction_forms_take_the_numerator():
|
||||
assert parse_page_range("[3/8]") == (3, 3)
|
||||
assert parse_page_range("(3/8)") == (3, 3)
|
||||
assert parse_page_range("page 3 of 8") == (3, 3)
|
||||
assert parse_page_range("pg 3/8") == (3, 3)
|
||||
|
||||
|
||||
def test_no_page_keyword_is_none():
|
||||
# Bare numbers / non-page words must not match (false positives are worse).
|
||||
assert parse_page_range("My Comic Part Three") is None
|
||||
assert parse_page_range("Episode 5") is None
|
||||
assert parse_page_range("Released 2024") is None
|
||||
assert parse_page_range("Part 3") is None
|
||||
|
||||
|
||||
def test_empty_and_none():
|
||||
assert parse_page_range("") is None
|
||||
assert parse_page_range(None) is None
|
||||
Reference in New Issue
Block a user