""" 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))