goon/tests/test_normalize.py
goon-foss d2b3378d93 fix(ingest): czysc opisy z encji i znacznikow HTML, title_normalized zawsze przez normalize
Opisy scen szly do bazy z `&#34;`, `&lt;br&gt;` i surowymi `<br />`, a apka i strona
SEO pokazuja opis jako tekst, wiec bylo to widac doslownie. clean_description
dekoduje encje, `<br>` i koniec akapitu zamienia na nowa linie, a znane znaczniki
wycina (`<3` zostaje tekstem). Wpiete w normalize_scene i normalize_movie.

bulk_rescrape_hqporner.py i repair_truncated_titles.py liczyly title_normalized
jako lower(title), przez co 6005 scen mialo interpunkcje w kluczu dopasowania.
Oba licza teraz normalize() i dekoduja encje w tytule.

Skrypty: fix_html_entity_titles.py przemianowany na fix_html_entities.py z
przebiegiem po opisach; nowy fix_title_normalized.py przelicza klucz tam, gdzie
rozjechal sie z normalize(title).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 15:59:39 +02:00

135 lines
5 KiB
Python

import pytest
from app.normalize.text import (
clean_description,
normalize,
normalize_person,
person_named_in,
slugify,
strip_accents,
unescape_html,
)
@pytest.mark.parametrize(
("raw", "expected"),
[
("Café Noir", "cafe noir"),
("naïve", "naive"),
("Łódź", "lodz"),
],
)
def test_strip_accents_and_lower(raw: str, expected: str) -> None:
assert normalize(raw) == expected
def test_normalize_drops_leading_articles() -> None:
assert normalize("The Big Lebowski") == "big lebowski"
assert normalize("A Quick One") == "quick one"
assert normalize("An Honest Take") == "honest take"
def test_normalize_collapses_whitespace_and_punct() -> None:
assert normalize("Hello, world!!!") == "hello world"
assert normalize("foo--bar__baz") == "foo bar baz"
def test_normalize_handles_none_and_empty() -> None:
assert normalize(None) == ""
assert normalize("") == ""
assert normalize(" ") == ""
def test_normalize_person_keeps_initials() -> None:
# „Mia M." → „mia m" (kropka usunięta jako interpunkcja)
assert normalize_person("Mia M.") == "mia m"
assert normalize_person("Mia Malkova") == "mia malkova"
def test_slugify_is_url_safe() -> None:
assert slugify("Brazzers Network") == "brazzers-network"
assert slugify("Mia Malkova") == "mia-malkova"
assert slugify("") == ""
def test_strip_accents_idempotent() -> None:
once = strip_accents("naïve")
twice = strip_accents(once)
assert once == twice == "naive"
@pytest.mark.parametrize(
("title", "person", "expected"),
[
("Savannah Bond Takes It All", "Savanna Bond", True),
("Jynx Maze Anal", "Jyn Maze", True),
("Julia Ann and Bridgette B", "Julia B.", False),
("Kim Gold Solo", "Goldy Kim", False),
# Jednowyrazowe nazwisko: tylko co do znaku. „Thebdck" lapal kazde „the".
("Twins Sasha Paige Share the Dick ft. Martin Spell", "Thebdck", False),
("Twins Sasha Paige Share the Dick ft. Martin Spell", "Sashamalone", False),
("Twins Sasha Paige Share the Dick ft. Martin Spell", "Dickxy", False),
("Twins Sasha Paige Share the Dick ft. Martin Spell", "Sasha Paige", True),
("Mandingo Destroys Her", "Mandingo", True),
# Zlepek sxyprn rowny sklejce sasiednich czlonow tytulu.
("Joanna Angel Rides Again", "JoannaAngel", True),
("Bonnie B Solo", "Bonnieb", True),
("Joanna Smith and Angel Dark", "JoannaAngel", False),
("Mandingos Best", "Mandingo", False),
# Przedrostek z limitem dlugosci: wariant zapisu tak, doklejone slowo nie.
("Alexis Crystal Returns", "Alexis Crystalline", False),
("Sasha Grey Returns", "Sash Grey", True),
],
)
def test_person_named_in(title: str, person: str, expected: bool) -> None:
assert person_named_in(title, person) is expected
@pytest.mark.parametrize(
("raw", "expected"),
[
# xvideos JSON-LD, encje HTML5.
(
"Are you sure we&apos;re allowed to do this mister Steve&quest;&quest; &lbrack;AI&rsqb;",
"Are you sure we're allowed to do this mister Steve?? [AI]",
),
("The BEST &lpar;&lpar; HOT WIFE &rpar;&rpar;", "The BEST (( HOT WIFE ))"),
# Podwojne kodowanie.
("Barbara &amp;amp; Jake &amp;lpar;4K&amp;rpar;", "Barbara & Jake (4K)"),
("&#039;x&#x27; &#34;q&#34;", "'x' \"q\""),
# .title() na zakodowanym tytule.
("Alix &Amp; Cherie", "Alix & Cherie"),
("&nbsp;Hot&nbsp;Wife&#65279;", "Hot Wife"),
# Bez srednika albo nieznana nazwa: zostaje jak bylo.
("Me&notice Tom & Jerry &copyright; Q&A", "Me&notice Tom & Jerry &copyright; Q&A"),
("", ""),
(None, ""),
],
)
def test_unescape_html(raw: str | None, expected: str) -> None:
assert unescape_html(raw) == expected
@pytest.mark.parametrize(
("raw", "expected"),
[
('Manny in &#34;MANUEL CREAMPIES 2&#34;. She loved it.', 'Manny in "MANUEL CREAMPIES 2". She loved it.'),
# Znaczniki zakodowane jako encje.
("Is it love?&lt;br&gt;Eveline only ordered", "Is it love?\nEveline only ordered"),
("door.&lt;/br&gt;&lt;/br&gt;Jessica", "door.\n\nJessica"),
("wash.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;She", "wash.\n\nShe"),
# Surowe znaczniki.
("Welcome, Carlo!<br />&nbsp;", "Welcome, Carlo!"),
('<p>See <a href="/x">Bunny</a> &amp; <b>more</b>!</p>', "See Bunny & more!"),
# To nie sa znaczniki.
("I miss you so much &lt;3.", "I miss you so much <3."),
("It was <a lot of fun> and <b careful>", "It was <a lot of fun> and <b careful>"),
('<span style="color: red" class=x>Hot</span>', "Hot"),
("&lt;Yokomiya-san. SSSP! No Cuts]", "<Yokomiya-san. SSSP! No Cuts]"),
("lips&tongues; Alix &Amp; Cherie", "lips&tongues; Alix & Cherie"),
("&nbsp;", None),
(None, None),
],
)
def test_clean_description(raw: str | None, expected: str | None) -> None:
assert clean_description(raw) == expected