diff --git a/README.md b/README.md index d8993dc..985d40d 100644 --- a/README.md +++ b/README.md @@ -121,4 +121,17 @@ character_details/ - Aerith Gainsborough — Final Fantasy VII - Princess Peach — Super Mario -- Sucy Manbavaran — Little Witch Academia \ No newline at end of file +- Sucy Manbavaran — Little Witch Academia +- Tokai Teio — Uma Musume +- Marth — Fire Emblem +- Asuka — Senran Kagura +- Hatsune Miku — Vocaloid +- Goku — Dragon Ball +- Jinx — League of Legends +- Ryu — Street Fighter +- Sonic — Sonic the Hedgehog +- Anya Forger — Spy x Family +- Link — The Legend of Zelda +- Geralt of Rivia — The Witcher +- Samus Aran — Metroid +- Pikachu — Pokemon \ No newline at end of file diff --git a/USER_GUIDE.md b/USER_GUIDE.md index 8f46fd9..3e5b10b 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -154,12 +154,24 @@ Produces a structured reference document for roleplay or creative writing. Inclu The following franchises have a dedicated Fandom wiki mapped for richer data: -| Franchise | Wiki | -|---|---| -| Final Fantasy VII / FF7 / FFVII | finalfantasy.fandom.com | -| Final Fantasy | finalfantasy.fandom.com | -| Super Mario / Mario | mario.fandom.com | -| Little Witch Academia / LWA | little-witch-academia.fandom.com | +| Franchise | Wiki | Aliases | +|---|---|---| +| Final Fantasy (I–XVI) | finalfantasy.fandom.com | final fantasy 1–16, ffi–ffxvi, ff1–ff16 | +| Super Mario | mario.fandom.com | mario, super mario | +| Little Witch Academia | little-witch-academia.fandom.com | lwa | +| Uma Musume | umamusume.fandom.com | uma musume pretty derby | +| Fire Emblem | fireemblem.fandom.com | | +| Senran Kagura | senrankagura.fandom.com | | +| Vocaloid | vocaloid.fandom.com | | +| Dragon Ball | dragonball.fandom.com | dragon ball z, dbz, dragon ball super, dbs | +| League of Legends | leagueoflegends.fandom.com | lol | +| Street Fighter | streetfighter.fandom.com | | +| Sonic | sonic.fandom.com | sonic the hedgehog | +| Spy x Family | spy-x-family.fandom.com | spy family, spyxfamily | +| The Legend of Zelda | zelda.fandom.com | zelda, legend of zelda | +| The Witcher | witcher.fandom.com | witcher | +| Metroid | metroid.fandom.com | | +| Pokemon | pokemon.fandom.com | pokémon | Characters from other franchises will still work using Wikipedia as the data source. The data will be less detailed but usable. diff --git a/src/character_details/fetcher.py b/src/character_details/fetcher.py index 911708c..f4858e8 100644 --- a/src/character_details/fetcher.py +++ b/src/character_details/fetcher.py @@ -25,15 +25,102 @@ HEADERS = { # Map franchise keywords -> Fandom community subdomain FRANCHISE_WIKIS: dict[str, str] = { + "final fantasy": "finalfantasy", + "final fantasy i": "finalfantasy", + "final fantasy 1": "finalfantasy", + "ffi": "finalfantasy", + "ff1": "finalfantasy", + "final fantasy ii": "finalfantasy", + "final fantasy 2": "finalfantasy", + "ffii": "finalfantasy", + "ff2": "finalfantasy", + "final fantasy iii": "finalfantasy", + "final fantasy 3": "finalfantasy", + "ffiii": "finalfantasy", + "ff3": "finalfantasy", + "final fantasy iv": "finalfantasy", + "final fantasy 4": "finalfantasy", + "ffiv": "finalfantasy", + "ff4": "finalfantasy", + "final fantasy v": "finalfantasy", + "final fantasy 5": "finalfantasy", + "ffv": "finalfantasy", + "ff5": "finalfantasy", + "final fantasy vi": "finalfantasy", + "final fantasy 6": "finalfantasy", + "ffvi": "finalfantasy", + "ff6": "finalfantasy", "final fantasy vii": "finalfantasy", "final fantasy 7": "finalfantasy", - "ff7": "finalfantasy", "ffvii": "finalfantasy", - "final fantasy": "finalfantasy", + "ff7": "finalfantasy", + "final fantasy viii": "finalfantasy", + "final fantasy 8": "finalfantasy", + "ffviii": "finalfantasy", + "ff8": "finalfantasy", + "final fantasy ix": "finalfantasy", + "final fantasy 9": "finalfantasy", + "ffix": "finalfantasy", + "ff9": "finalfantasy", + "final fantasy x": "finalfantasy", + "final fantasy 10": "finalfantasy", + "ffx": "finalfantasy", + "ff10": "finalfantasy", + "final fantasy xi": "finalfantasy", + "final fantasy 11": "finalfantasy", + "ffxi": "finalfantasy", + "ff11": "finalfantasy", + "final fantasy xii": "finalfantasy", + "final fantasy 12": "finalfantasy", + "ffxii": "finalfantasy", + "ff12": "finalfantasy", + "final fantasy xiii": "finalfantasy", + "final fantasy 13": "finalfantasy", + "ffxiii": "finalfantasy", + "ff13": "finalfantasy", + "final fantasy xiv": "finalfantasy", + "final fantasy 14": "finalfantasy", + "ffxiv": "finalfantasy", + "ff14": "finalfantasy", + "final fantasy xv": "finalfantasy", + "final fantasy 15": "finalfantasy", + "ffxv": "finalfantasy", + "ff15": "finalfantasy", + "final fantasy xvi": "finalfantasy", + "final fantasy 16": "finalfantasy", + "ffxvi": "finalfantasy", + "ff16": "finalfantasy", "super mario": "mario", "mario": "mario", "little witch academia": "little-witch-academia", "lwa": "little-witch-academia", + "uma musume": "umamusume", + "umamusume": "umamusume", + "uma musume pretty derby": "umamusume", + "fire emblem": "fireemblem", + "senran kagura": "senrankagura", + "vocaloid": "vocaloid", + "dragon ball": "dragonball", + "dragon ball z": "dragonball", + "dbz": "dragonball", + "dragon ball super": "dragonball", + "dbs": "dragonball", + "league of legends": "leagueoflegends", + "lol": "leagueoflegends", + "street fighter": "streetfighter", + "sonic": "sonic", + "sonic the hedgehog": "sonic", + "spy x family": "spy-x-family", + "spy family": "spy-x-family", + "spyxfamily": "spy-x-family", + "zelda": "zelda", + "the legend of zelda": "zelda", + "legend of zelda": "zelda", + "witcher": "witcher", + "the witcher": "witcher", + "metroid": "metroid", + "pokemon": "pokemon", + "pokémon": "pokemon", } # Section title keywords -> model field diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_wiki_fetch.py b/tests/test_wiki_fetch.py new file mode 100644 index 0000000..2636037 --- /dev/null +++ b/tests/test_wiki_fetch.py @@ -0,0 +1,51 @@ +""" +Integration tests — hit each Fandom wiki endpoint and verify we get +meaningful character data back. + +These tests make real HTTP requests, so they require network access. +""" + +import pytest +import pytest_asyncio + +from character_details.fetcher import fetch_character +from character_details.models import CharacterData + +CHARACTERS = [ + ("Tifa Lockhart", "Final Fantasy VII"), + ("Y'shtola Rhul", "Final Fantasy XIV"), + ("Princess Peach", "Super Mario"), + ("Sucy Manbavaran", "Little Witch Academia"), + ("Rice Shower", "Uma Musume"), + ("Camilla", "Fire Emblem"), + ("Shiki", "Senran Kagura"), + ("Hatsune Miku", "Vocaloid"), + ("Android 18", "Dragon Ball"), + ("Jinx", "League of Legends"), + ("Chun-Li", "Street Fighter"), + ("Rouge the Bat", "Sonic"), + ("Yor Briar", "Spy x Family"), + ("Princess Zelda", "The Legend of Zelda"), + ("Ciri", "The Witcher"), + ("Zero Suit Samus", "Metroid"), + ("Nessa", "Pokemon"), +] + + +@pytest.mark.asyncio +@pytest.mark.parametrize("name,franchise", CHARACTERS, ids=[f"{n} ({f})" for n, f in CHARACTERS]) +async def test_fetch_character(name: str, franchise: str): + """Each wiki should return a CharacterData with at least a name, franchise, description, and source URL.""" + result = await fetch_character(name, franchise) + + assert isinstance(result, CharacterData) + assert result.name == name + assert result.franchise == franchise + assert result.description, f"No description returned for {name} ({franchise})" + assert len(result.sources) >= 1, f"No sources returned for {name} ({franchise})" + + # At least one source should be a Fandom URL (not just Wikipedia) + fandom_sources = [s for s in result.sources if "fandom.com" in s] + assert fandom_sources, ( + f"No Fandom source for {name} ({franchise}) — got: {result.sources}" + )