fix: trim a credit list in search_author to the first name (#1290)

Fixes #1252 for the case in the second report.

`_pick_search_author` returns `search_author` untouched but trims
`authors[0]` to its first comma-separated name. So the same credit list
searches differently depending on which field carries it:

```
via authors[0]     -> "Blindness Jose Saramago"
via search_author  -> "Blindness Jose Saramago, Giovanni Pontiero, Zohreh Eftekhari"
```

Anna's Archive answers the second one with nothing. That is the query in
@theDoz12's log, and it explains the shape of the report: the bypass
succeeds, the search runs, and the UI still says no releases. Nothing in
the download path is broken, the query simply cannot match.

Measured against live AA on 1.3.14, same book, same source, only the
field carrying the author changed:

| query | releases |
| --- | --- |
| `Blindness Jose Saramago, Giovanni Pontiero, Zohreh Eftekhari` | 0 |
| `Blindness Jose Saramago` | 49 |
| `Blindness` | 50 |

With the patch the second form is produced from either field, and the
same search returns 49.

Three regression tests added, including one that asserts both fields
yield the same query. On `tests/core/test_search_plan.py` the run goes
from 5 failures to 3; the 3 that remain are the language tests, which
fail identically with and without this change on my machine.

Worth saying what this does not cover: the first report in that issue
ends with `Found 2 releases via ISBN` and still shows nothing, so that
one is a different fault further along. I could not reproduce it here.
This commit is contained in:
Nicholas Velten
2026-09-01 11:08:39 -04:00
committed by GitHub
parent 3937ae119b
commit 69ff0d6a78
2 changed files with 59 additions and 8 deletions
+8 -8
View File
@@ -113,17 +113,17 @@ def _normalize_languages(languages: list[str] | None, user_id: int | None) -> li
def _pick_search_author(book: BookMetadata) -> str:
if book.search_author:
return book.search_author
if not book.authors:
author = book.search_author or (book.authors[0] if book.authors else "")
if not author:
return ""
first = book.authors[0]
if "," in first:
first = first.split(",")[0].strip()
# `search_author` can arrive as the display string for the whole credit list
# ("Author, Translator, Narrator"), which Anna's Archive answers with nothing at
# all. Trim it to the first name, which is what the authors list already gets.
if "," in author:
author = author.split(",")[0].strip()
return first
return author
def _pick_search_title(book: BookMetadata) -> str:
+51
View File
@@ -122,3 +122,54 @@ class TestReleaseSearchPlan:
plan = build_release_search_plan(book, languages=["fr"], user_id=7)
assert plan.languages == ["fr"]
class TestSearchAuthorNormalization:
"""A credit list must not reach the query, whichever field carries it.
Anna's Archive answers "Blindness Jose Saramago, Giovanni Pontiero, ..." with
nothing, so a book whose author string lists translators alongside the author
finds no releases at all.
"""
MULTI = "Jose Saramago, Giovanni Pontiero, Zohreh Eftekhari"
def test_search_author_is_trimmed_to_the_first_name(self):
book = BookMetadata(
provider="manual",
provider_id="1",
title="Blindness",
authors=[self.MULTI],
search_author=self.MULTI,
)
assert build_release_search_plan(book).primary_query == "Blindness Jose Saramago"
def test_search_author_matches_the_authors_list(self):
"""Same credit list, two fields, one query."""
via_authors = BookMetadata(
provider="manual", provider_id="1", title="Blindness", authors=[self.MULTI]
)
via_search_author = BookMetadata(
provider="manual",
provider_id="1",
title="Blindness",
authors=[self.MULTI],
search_author=self.MULTI,
)
assert (
build_release_search_plan(via_search_author).primary_query
== build_release_search_plan(via_authors).primary_query
)
def test_single_author_is_untouched(self):
book = BookMetadata(
provider="manual",
provider_id="1",
title="Elantris",
authors=["Brandon Sanderson"],
search_author="Brandon Sanderson",
)
assert build_release_search_plan(book).primary_query == "Elantris Brandon Sanderson"