243 lines
8.4 KiB
Python
243 lines
8.4 KiB
Python
import pytest
|
|
from django.conf import settings
|
|
from django.core.cache import cache
|
|
from django.test import override_settings
|
|
from django.urls import reverse
|
|
from django.utils import timezone
|
|
|
|
from apps.jobs.models import JobPosting, ScoreRun
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.django_db
|
|
def test_today_requires_login(client):
|
|
response = client.get(reverse("dashboard:today"))
|
|
assert response.status_code == 302
|
|
assert reverse("login") in response.url
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.django_db
|
|
def test_browser_favicon_and_product_brand_assets_are_local(client):
|
|
response = client.get("/favicon.ico")
|
|
assert response.status_code == 301
|
|
assert response.url.endswith("/static/favicon.svg")
|
|
|
|
favicon = (settings.BASE_DIR / "static" / "favicon.svg").read_text(encoding="utf-8")
|
|
product_logo = (settings.BASE_DIR / "static" / "img" / "vacatureradar-logo.svg").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
wordmark = settings.BASE_DIR / "static" / "img" / "itworx-wordmark.png"
|
|
for signature in ('stroke="#19d3e6"', 'stroke="#7df5ff"', 'fill="#45d69b"'):
|
|
assert signature in favicon
|
|
assert 'stroke="#19d3e6"' in product_logo
|
|
assert 'fill="#36c990"' in product_logo
|
|
assert "<rect" not in product_logo
|
|
assert 'viewBox="0 0 64 64"' in favicon
|
|
assert wordmark.read_bytes().startswith(b"\x89PNG\r\n\x1a\n")
|
|
assert wordmark.stat().st_size < 100_000
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.django_db
|
|
def test_today_loads_for_user(client, user, profile):
|
|
client.force_login(user)
|
|
response = client.get(reverse("dashboard:today"))
|
|
assert response.status_code == 200
|
|
assert "Vandaag" in response.content.decode()
|
|
body = response.content.decode()
|
|
assert 'aria-label="VacatureRadar dashboard"' in body
|
|
assert "Intelligence cockpit" in body
|
|
assert "Ontwikkeld door" in body
|
|
assert 'aria-label="Ontwikkeld door ITWorx.tech"' in body
|
|
assert 'class="sidebar-footer"' in body
|
|
assert 'class="sidebar-account"' not in body
|
|
assert "Dagoverzicht" in body
|
|
assert 'content="VacatureRadar"' in body
|
|
|
|
|
|
def test_premium_layout_uses_one_bounded_ultrawide_grid():
|
|
css = (settings.BASE_DIR / "static" / "css" / "premium.css").read_text(encoding="utf-8")
|
|
assert "--content-frame: 1720px" in css
|
|
assert "width: min(100%, var(--content-frame))" in css
|
|
assert "margin-inline: auto" in css
|
|
assert ".topbar-inner" in css
|
|
assert "--content-frame: 2200px" in css
|
|
assert "--content-frame: 2480px" in css
|
|
assert "@media (min-width: 1800px)" in css
|
|
assert "minmax(650px, 880px) minmax(760px, 1fr)" in css
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.django_db
|
|
def test_demo_banner_is_part_of_main_content_flow(client, user, settings):
|
|
settings.DEMO_MODE_ENABLED = True
|
|
settings.DEMO_READ_ONLY = True
|
|
settings.DEMO_USERNAME = user.username
|
|
client.force_login(user)
|
|
response = client.get(reverse("dashboard:today"))
|
|
body = response.content.decode()
|
|
|
|
assert response.status_code == 200
|
|
assert body.index('<main id="main"') < body.index('class="demo-mode-banner"')
|
|
assert 'role="status" aria-label="Publieke demo, alleen-lezen"' in body
|
|
|
|
|
|
def test_css_imports_share_the_release_cache_version():
|
|
css = (settings.BASE_DIR / "static" / "css" / "app.css").read_text(encoding="utf-8")
|
|
assert css.count("?v=0.3.13") == 7
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.django_db
|
|
def test_job_list_defaults_to_ranked_it_focus_but_keeps_explicit_archive(
|
|
client, user, profile, job
|
|
):
|
|
non_it = JobPosting.objects.create(
|
|
employer=job.employer,
|
|
original_title="Spontane sollicitatie - logopedist",
|
|
normalized_title="spontane sollicitatie logopedist",
|
|
canonical_url="https://jobs.example.org/vacatures/logopedist",
|
|
canonical_key="9" * 64,
|
|
content_hash="8" * 64,
|
|
description_text="Zorgfunctie met toegang tot digitale patiëntendossiers.",
|
|
status=JobPosting.Status.ACTIVE,
|
|
)
|
|
misleading_it_title = JobPosting.objects.create(
|
|
employer=job.employer,
|
|
original_title="IT Recruitment Specialist",
|
|
normalized_title="it recruitment specialist",
|
|
canonical_url="https://jobs.example.org/vacatures/it-recruitment",
|
|
canonical_key="3" * 64,
|
|
content_hash="2" * 64,
|
|
status=JobPosting.Status.ACTIVE,
|
|
)
|
|
ScoreRun.objects.create(
|
|
job=job,
|
|
profile=profile,
|
|
profile_version=profile.version,
|
|
score=60,
|
|
confidence=0.9,
|
|
recommendation=ScoreRun.Recommendation.WEAK,
|
|
positives=["IT-focus bevestigd via de functietitel."],
|
|
)
|
|
ScoreRun.objects.create(
|
|
job=non_it,
|
|
profile=profile,
|
|
profile_version=profile.version,
|
|
score=99,
|
|
confidence=0.9,
|
|
recommendation=ScoreRun.Recommendation.POSSIBLE,
|
|
)
|
|
hidden_it = JobPosting.objects.create(
|
|
employer=job.employer,
|
|
original_title="Cloud Engineer buiten de zoekradius",
|
|
normalized_title="cloud engineer buiten de zoekradius",
|
|
canonical_url="https://jobs.example.org/vacatures/cloud-ver",
|
|
canonical_key="5" * 64,
|
|
content_hash="4" * 64,
|
|
status=JobPosting.Status.ACTIVE,
|
|
)
|
|
ScoreRun.objects.create(
|
|
job=hidden_it,
|
|
profile=profile,
|
|
profile_version=profile.version,
|
|
score=98,
|
|
confidence=0.9,
|
|
recommendation=ScoreRun.Recommendation.HIDDEN,
|
|
hard_exclusions=["Buiten de zoekradius."],
|
|
)
|
|
client.force_login(user)
|
|
|
|
focused = client.get(reverse("jobs:list"))
|
|
focused_body = focused.content.decode()
|
|
assert job.original_title in focused_body
|
|
assert non_it.original_title not in focused_body
|
|
assert misleading_it_title.original_title not in focused_body
|
|
assert hidden_it.original_title not in focused_body
|
|
assert focused.context["it_job_count"] == 2
|
|
assert focused.context["filtered_non_it_count"] == 2
|
|
|
|
archive = client.get(reverse("jobs:list"), {"focus": "all", "match": "all"})
|
|
archive_body = archive.content.decode()
|
|
assert non_it.original_title in archive_body
|
|
assert misleading_it_title.original_title in archive_body
|
|
assert hidden_it.original_title in archive_body
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.django_db
|
|
def test_today_uses_latest_user_score_and_excludes_non_it(client, user, profile, job):
|
|
non_it = JobPosting.objects.create(
|
|
employer=job.employer,
|
|
original_title="Technieker Facilitaire Diensten",
|
|
normalized_title="technieker facilitaire diensten",
|
|
canonical_url="https://jobs.example.org/vacatures/facilitair",
|
|
canonical_key="7" * 64,
|
|
content_hash="6" * 64,
|
|
status=JobPosting.Status.ACTIVE,
|
|
first_seen=timezone.now(),
|
|
)
|
|
ScoreRun.objects.create(
|
|
job=job,
|
|
profile=profile,
|
|
profile_version=profile.version,
|
|
score=70,
|
|
confidence=0.9,
|
|
recommendation=ScoreRun.Recommendation.POSSIBLE,
|
|
)
|
|
ScoreRun.objects.create(
|
|
job=job,
|
|
profile=profile,
|
|
profile_version=profile.version,
|
|
score=66,
|
|
confidence=0.9,
|
|
recommendation=ScoreRun.Recommendation.POSSIBLE,
|
|
)
|
|
ScoreRun.objects.create(
|
|
job=non_it,
|
|
profile=profile,
|
|
profile_version=profile.version,
|
|
score=95,
|
|
confidence=0.9,
|
|
recommendation=ScoreRun.Recommendation.STRONG,
|
|
)
|
|
client.force_login(user)
|
|
|
|
response = client.get(reverse("dashboard:today"))
|
|
|
|
cards = list(response.context["score_cards"])
|
|
assert [card.job_id for card in cards] == [job.id]
|
|
assert cards[0].score == 66
|
|
assert response.context["filtered_non_it_jobs"] == 1
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.django_db
|
|
@override_settings(
|
|
AUTH_LOGIN_RATE_LIMIT_MAX_ATTEMPTS=2,
|
|
AUTH_LOGIN_RATE_LIMIT_WINDOW_SECONDS=120,
|
|
AUTH_LOGIN_RATE_LIMIT_BLOCK_SECONDS=300,
|
|
)
|
|
def test_login_view_rate_limits_failed_attempts(client, user):
|
|
cache.clear()
|
|
|
|
response = client.post(
|
|
reverse("login"),
|
|
{"username": user.username, "password": "wrong"},
|
|
)
|
|
assert response.status_code == 200
|
|
|
|
response = client.post(
|
|
reverse("login"),
|
|
{"username": user.username, "password": "wrong"},
|
|
)
|
|
body = response.content.decode("utf-8")
|
|
assert "Te veel inlogpogingen" in body
|
|
|
|
response = client.post(
|
|
reverse("login"),
|
|
{"username": user.username, "password": "wrong"},
|
|
)
|
|
assert "Te veel inlogpogingen" in response.content.decode("utf-8")
|