fix(search): typed text is a literal in every LIKE pattern
The feed's channel-name match and the live search's term fold built their LIKE pattern from the typed text as it was, so `_` and `%` acted as wildcards: `build_log` also matched `buildXlog`. The term fold also tested a substring, so a term already present inside a longer word (`cat` in `category`) was never folded into search_terms. It now compares whole terms, escaped. `like_escape` moves from the Plex query module to `app.utils`, beside `to_tsquery_str`, and every pattern built from typed text goes through it.
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
"""What a person types is a literal inside a LIKE pattern, never a wildcard (DB-lane).
|
||||
|
||||
`%` and `_` are LIKE wildcards. The feed's channel-name match and the live search's term fold both
|
||||
built their pattern from the typed text as it was, so `my_channel` also matched `myXchannel`, and a
|
||||
term already present as part of a longer word was never folded in.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.models import Channel, Subscription, Video
|
||||
from app.routes.search import _fold_term
|
||||
|
||||
|
||||
def _video(vid: str, ch: str, title: str, search_terms: str | None = None) -> Video:
|
||||
return Video(
|
||||
id=vid,
|
||||
channel_id=ch,
|
||||
published_at=datetime(2024, 1, 1, tzinfo=timezone.utc),
|
||||
title=title,
|
||||
is_short=False,
|
||||
live_status="none",
|
||||
via_search=False,
|
||||
via_explore=False,
|
||||
search_terms=search_terms,
|
||||
)
|
||||
|
||||
|
||||
def test_an_underscore_in_a_feed_search_matches_only_itself(db, as_user, make_user):
|
||||
user = make_user()
|
||||
db.add_all(
|
||||
[
|
||||
Channel(id="UCunder", title="build_log daily", subscriber_count=1),
|
||||
Channel(id="UClookalike", title="buildXlog daily", subscriber_count=1),
|
||||
]
|
||||
)
|
||||
db.add_all(
|
||||
[
|
||||
Subscription(user_id=user.id, channel_id=ch, priority=0, hidden=False)
|
||||
for ch in ("UCunder", "UClookalike")
|
||||
]
|
||||
)
|
||||
db.add_all(
|
||||
[
|
||||
_video("vidUnder", "UCunder", "Nothing in common"),
|
||||
_video("vidLook", "UClookalike", "Nothing in common either"),
|
||||
]
|
||||
)
|
||||
db.commit()
|
||||
|
||||
resp = as_user(user).get("/api/feed?q=build_log&show=all&limit=50")
|
||||
|
||||
assert resp.status_code == 200, resp.text
|
||||
assert {it["id"] for it in resp.json()["items"]} == {"vidUnder"}
|
||||
|
||||
|
||||
def test_a_term_is_folded_in_unless_it_is_already_a_whole_term(db):
|
||||
db.add(Channel(id="UCfold", title="Fold", subscriber_count=1))
|
||||
db.add_all(
|
||||
[
|
||||
_video("vidWord", "UCfold", "a", search_terms="category"),
|
||||
_video("vidWild", "UCfold", "b", search_terms="catXdog"),
|
||||
_video("vidHas", "UCfold", "c", search_terms="old cat_dog new"),
|
||||
_video("vidNone", "UCfold", "d"),
|
||||
]
|
||||
)
|
||||
db.commit()
|
||||
|
||||
_fold_term(db, ["vidWord", "vidWild", "vidHas", "vidNone"], "cat")
|
||||
_fold_term(db, ["vidWild", "vidHas"], "cat_dog")
|
||||
db.commit()
|
||||
db.expire_all()
|
||||
|
||||
terms = dict(db.execute(select(Video.id, Video.search_terms)).all())
|
||||
assert terms == {
|
||||
"vidWord": "category cat",
|
||||
"vidWild": "catXdog cat cat_dog",
|
||||
"vidHas": "old cat_dog new cat",
|
||||
"vidNone": "cat",
|
||||
}
|
||||
Reference in New Issue
Block a user