Files
cyberyan_linkedin_search/backend/tests/test_query_builder.py
T

77 lines
2.9 KiB
Python
Raw Normal View History

"""
Unit tests for the Elasticsearch query builder.
``build_query`` is a pure function, so the ranking and filtering rules can be
asserted without a running cluster - the fastest possible feedback loop for the
part of the system that is easiest to get subtly wrong.
"""
from __future__ import annotations
from apps.search.backends.elastic import build_query
from apps.search.types import SORT_EXPERIENCE_DESC, SORT_NAME, SearchRequest
def _bool(request: SearchRequest) -> dict:
return build_query(request)["query"]["bool"]
def test_empty_query_matches_everything() -> None:
clause = _bool(SearchRequest())
assert clause["must"] == [{"match_all": {}}]
assert clause["filter"] == []
def test_keyword_query_uses_fuzzy_multi_match_and_phrase_boost() -> None:
clause = _bool(SearchRequest(q="python developer"))
multi_match = clause["must"][0]["multi_match"]
assert multi_match["query"] == "python developer"
assert multi_match["fuzziness"] == "AUTO"
assert any(field.startswith("full_name^") for field in multi_match["fields"])
# The phrase clause only boosts; it must never be required.
assert clause["should"][0]["multi_match"]["type"] == "phrase"
def test_filters_are_terms_clauses_in_filter_context() -> None:
clause = _bool(
SearchRequest(skills=("python", "django"), countries=("germany",), seniority=("senior",))
)
assert {"terms": {"skills.keyword": ["python", "django"]}} in clause["filter"]
assert {"terms": {"location_country": ["germany"]}} in clause["filter"]
assert {"terms": {"seniority_levels": ["senior"]}} in clause["filter"]
# Filters must not leak into the scoring clauses.
assert clause["must"] == [{"match_all": {}}]
def test_experience_range_is_translated_to_a_range_filter() -> None:
clause = _bool(SearchRequest(min_experience=5, max_experience=10))
assert {"range": {"years_experience": {"gte": 5, "lte": 10}}} in clause["filter"]
open_ended = _bool(SearchRequest(min_experience=5))
assert {"range": {"years_experience": {"gte": 5}}} in open_ended["filter"]
def test_pagination_maps_to_from_and_size() -> None:
body = build_query(SearchRequest(page=3, page_size=25))
assert body["from"] == 50
assert body["size"] == 25
assert body["track_total_hits"] is True
def test_sort_options() -> None:
assert build_query(SearchRequest(sort=SORT_NAME))["sort"][0] == {"full_name.keyword": "asc"}
assert build_query(SearchRequest(sort=SORT_EXPERIENCE_DESC))["sort"][0] == {
"years_experience": {"order": "desc", "missing": "_last"}
}
assert build_query(SearchRequest())["sort"][0] == "_score"
def test_highlight_only_requested_with_a_keyword() -> None:
assert "highlight" not in build_query(SearchRequest())
assert "highlight" in build_query(SearchRequest(q="python"))
def test_facets_can_be_switched_off() -> None:
assert "aggs" in build_query(SearchRequest())
assert "aggs" not in build_query(SearchRequest(include_facets=False))