146 lines
5.5 KiB
Python
146 lines
5.5 KiB
Python
|
|
"""
|
||
|
|
End-to-end tests of the search endpoint against the PostgreSQL backend.
|
||
|
|
|
||
|
|
They pin down the behaviour the task description asks for: keyword search plus
|
||
|
|
at least two filters, and a correct contract between frontend and backend.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from django.urls import reverse
|
||
|
|
|
||
|
|
pytestmark = pytest.mark.django_db
|
||
|
|
|
||
|
|
|
||
|
|
def search(client, **params): # noqa: ANN001, ANN201
|
||
|
|
return client.get(reverse("v1:search:search"), params)
|
||
|
|
|
||
|
|
|
||
|
|
def names(response) -> list[str]: # noqa: ANN001
|
||
|
|
return [hit["full_name"] for hit in response.data["results"]]
|
||
|
|
|
||
|
|
|
||
|
|
def test_empty_query_returns_every_profile(
|
||
|
|
auth_client, analyst, sample_profiles
|
||
|
|
) -> None: # noqa: ANN001
|
||
|
|
response = search(auth_client(analyst))
|
||
|
|
assert response.status_code == 200
|
||
|
|
assert response.data["count"] == len(sample_profiles)
|
||
|
|
assert response.data["backend"] == "postgres"
|
||
|
|
|
||
|
|
|
||
|
|
def test_keyword_matches_skills_and_summary(
|
||
|
|
auth_client, analyst, sample_profiles
|
||
|
|
) -> None: # noqa: ANN001
|
||
|
|
response = search(auth_client(analyst), q="django")
|
||
|
|
assert names(response) == ["sara python"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_keyword_matches_a_person_name(
|
||
|
|
auth_client, analyst, sample_profiles
|
||
|
|
) -> None: # noqa: ANN001
|
||
|
|
assert names(search(auth_client(analyst), q="maria")) == ["maria frontend"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_keyword_tolerates_a_typo_in_the_name(
|
||
|
|
auth_client, analyst, sample_profiles
|
||
|
|
) -> None: # noqa: ANN001
|
||
|
|
"""Trigram similarity is what makes ``jhon`` still find ``john``."""
|
||
|
|
assert "john recruiter" in names(search(auth_client(analyst), q="jhon recruiter"))
|
||
|
|
|
||
|
|
|
||
|
|
def test_skill_filter(auth_client, analyst, sample_profiles) -> None: # noqa: ANN001
|
||
|
|
assert names(search(auth_client(analyst), skills="react")) == ["maria frontend"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_multiple_values_inside_one_filter_are_or_combined(
|
||
|
|
auth_client, analyst, sample_profiles
|
||
|
|
) -> None: # noqa: ANN001
|
||
|
|
response = search(auth_client(analyst), skills="react,django")
|
||
|
|
assert sorted(names(response)) == ["maria frontend", "sara python"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_different_filters_are_and_combined(
|
||
|
|
auth_client, analyst, sample_profiles
|
||
|
|
) -> None: # noqa: ANN001
|
||
|
|
response = search(auth_client(analyst), skills="react,django", countries="spain")
|
||
|
|
assert names(response) == ["maria frontend"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_role_and_experience_filters(auth_client, analyst, sample_profiles) -> None: # noqa: ANN001
|
||
|
|
response = search(auth_client(analyst), roles="engineering", min_experience=5)
|
||
|
|
assert names(response) == ["sara python"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_repeated_query_parameters_are_supported(
|
||
|
|
auth_client, analyst, sample_profiles
|
||
|
|
) -> None: # noqa: ANN001
|
||
|
|
client = auth_client(analyst)
|
||
|
|
response = client.get(f"{reverse('v1:search:search')}?skills=react&skills=django")
|
||
|
|
assert sorted(names(response)) == ["maria frontend", "sara python"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_facets_reflect_the_filtered_result_set(
|
||
|
|
auth_client, analyst, sample_profiles
|
||
|
|
) -> None: # noqa: ANN001
|
||
|
|
response = search(auth_client(analyst), roles="engineering")
|
||
|
|
countries = {bucket["key"] for bucket in response.data["facets"]["country"]}
|
||
|
|
assert countries == {"germany", "spain"}
|
||
|
|
assert response.data["facets"]["skills"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_sorting_by_experience(auth_client, analyst, sample_profiles) -> None: # noqa: ANN001
|
||
|
|
assert names(search(auth_client(analyst), sort="-experience"))[0] == "john recruiter"
|
||
|
|
assert names(search(auth_client(analyst), sort="experience"))[0] == "maria frontend"
|
||
|
|
assert names(search(auth_client(analyst), sort="name"))[0] == "john recruiter"
|
||
|
|
|
||
|
|
|
||
|
|
def test_pagination_metadata(auth_client, analyst, sample_profiles) -> None: # noqa: ANN001
|
||
|
|
response = search(auth_client(analyst), page_size=2, page=2, sort="name")
|
||
|
|
assert response.data["page"] == 2
|
||
|
|
assert response.data["page_size"] == 2
|
||
|
|
assert response.data["total_pages"] == 2
|
||
|
|
assert len(response.data["results"]) == 1
|
||
|
|
|
||
|
|
|
||
|
|
def test_highlight_is_returned_for_keyword_searches(
|
||
|
|
auth_client, analyst, sample_profiles
|
||
|
|
) -> None: # noqa: ANN001
|
||
|
|
hit = search(auth_client(analyst), q="django").data["results"][0]
|
||
|
|
assert "<mark>" in " ".join(hit["highlight"].get("skills", []))
|
||
|
|
|
||
|
|
|
||
|
|
def test_invalid_experience_range_is_rejected(auth_client, analyst) -> None: # noqa: ANN001
|
||
|
|
response = search(auth_client(analyst), min_experience=10, max_experience=2)
|
||
|
|
assert response.status_code == 400
|
||
|
|
assert response.data["error"]["code"] == "validation_error"
|
||
|
|
|
||
|
|
|
||
|
|
def test_page_size_is_capped(auth_client, analyst) -> None: # noqa: ANN001
|
||
|
|
assert search(auth_client(analyst), page_size=5000).status_code == 400
|
||
|
|
|
||
|
|
|
||
|
|
def test_deep_pagination_is_refused(auth_client, analyst) -> None: # noqa: ANN001
|
||
|
|
assert search(auth_client(analyst), page=99999, page_size=100).status_code == 400
|
||
|
|
|
||
|
|
|
||
|
|
def test_unknown_filter_values_return_an_empty_page_not_an_error(
|
||
|
|
auth_client, analyst, sample_profiles
|
||
|
|
) -> None: # noqa: ANN001
|
||
|
|
response = search(auth_client(analyst), skills="cobol")
|
||
|
|
assert response.status_code == 200
|
||
|
|
assert response.data["count"] == 0
|
||
|
|
|
||
|
|
|
||
|
|
def test_suggest_endpoint(auth_client, analyst, sample_profiles) -> None: # noqa: ANN001
|
||
|
|
response = auth_client(analyst).get(reverse("v1:search:suggest"), {"q": "mar"})
|
||
|
|
assert response.status_code == 200
|
||
|
|
assert "maria frontend" in response.data["suggestions"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_skill_dictionary_endpoint(auth_client, analyst, sample_profiles) -> None: # noqa: ANN001
|
||
|
|
response = auth_client(analyst).get(reverse("v1:profiles:skills"), {"q": "py"})
|
||
|
|
assert response.status_code == 200
|
||
|
|
assert any(item["name"] == "python" for item in response.data)
|