Files
VacatureRadar/tests/integration/test_tasks_and_more_views.py
T
Jens a4eced8be5
deploy / deploy (push) Canceled after 0s
Fix release blockers and deployment build
2026-07-21 21:22:29 +02:00

228 lines
8.0 KiB
Python

from datetime import timedelta
import pytest
from django.urls import reverse
from django.utils import timezone
from apps.jobs.models import Feedback, ScoreRun
from apps.jobs.tasks import rescore_active_jobs, update_job_lifecycle
from apps.notifications.tasks import build_due_digests
from apps.sources.models import RawDocument, Source, SourceLease, SourcePolicyReview, SourceRun
from apps.sources.services.fetcher import FetchedDocument, RateLimitedError
from apps.sources.tasks import cleanup_raw_documents, fetch_source, schedule_due_sources
@pytest.mark.django_db
def test_task_helpers(profile, job, source, monkeypatch):
result = rescore_active_jobs(profile.pk)
assert result["scores_created"] == 1
assert ScoreRun.objects.filter(job=job, profile=profile).exists()
assert update_job_lifecycle() == {"expired": 0, "uncertain": 0, "removed": 0}
calls = []
monkeypatch.setattr(
"apps.sources.tasks.fetch_source.delay", lambda source_id: calls.append(source_id)
)
source.next_run_at = timezone.now() - timedelta(minutes=1)
source.save(update_fields=["next_run_at"])
assert schedule_due_sources()["scheduled"] == 1
assert calls == [source.pk]
old = RawDocument.objects.create(
source=source,
kind=RawDocument.Kind.TEXT,
content_hash="8" * 64,
retain_until=timezone.now() - timedelta(days=1),
)
assert cleanup_raw_documents()["deleted"] >= 1
assert not RawDocument.objects.filter(pk=old.pk).exists()
source.policy = Source.Policy.DENY
source.policy_reason = "test"
source.save(update_fields=["policy", "policy_reason"])
assert fetch_source(source.pk)["status"] == "skipped"
assert SourceRun.objects.filter(source=source, status=SourceRun.Status.SKIPPED).exists()
def test_fetch_source_skips_when_lease_blocked(db, source, monkeypatch):
SourceLease.objects.create(
source=source,
token="active-worker",
worker_id="active-worker",
expires_at=timezone.now() + timedelta(minutes=2),
)
calls = {"count": 0}
def fake_fetch_url(*args, **kwargs):
calls["count"] += 1
return FetchedDocument(
requested_url=source.base_url,
final_url=source.base_url,
status_code=200,
headers={},
content=b"Vacature",
)
monkeypatch.setattr("apps.sources.tasks.fetch_url", fake_fetch_url)
assert fetch_source(source.pk)["status"] == "skipped"
assert calls["count"] == 0
assert SourceRun.objects.filter(source=source, status=SourceRun.Status.SKIPPED).exists()
def test_fetch_source_handles_rate_limit_backoff(db, source, monkeypatch):
called = {"count": 0}
def fake_fetch_url(*args, **kwargs):
called["count"] += 1
raise RateLimitedError(30)
monkeypatch.setattr("apps.sources.tasks.fetch_url", fake_fetch_url)
result = fetch_source(source.pk)
assert result["status"] == "rate_limited"
assert called["count"] == 1
source.refresh_from_db()
assert source.next_run_at is not None
assert SourceRun.objects.filter(source=source, status=SourceRun.Status.FAILED).exists()
def test_fetch_source_resets_fail_state_on_304(db, source, monkeypatch):
source.failure_count = 4
source.save(update_fields=["failure_count"])
def fake_fetch_url(*args, **kwargs):
return FetchedDocument(
requested_url=source.base_url,
final_url=source.base_url,
status_code=304,
headers={},
content=b"",
)
monkeypatch.setattr("apps.sources.tasks.fetch_url", fake_fetch_url)
assert fetch_source(source.pk)["status"] == "not_modified"
source.refresh_from_db()
assert source.failure_count == 0
def test_fetch_source_is_blocked_by_conflicting_review(db):
source = Source.objects.create(
name="Blocked source",
source_type=Source.Type.EMPLOYER,
base_url="https://jobs.example.org/vacatures/",
domain="jobs.example.org",
status=Source.Status.ACTIVE,
policy=Source.Policy.ALLOW,
)
SourcePolicyReview.objects.create(
source=source,
decision=SourcePolicyReview.Decision.DENY,
reason="Review blokkeert deze bron",
)
result = fetch_source(source.pk)
assert result["status"] == "skipped"
assert SourceRun.objects.filter(source=source, status=SourceRun.Status.SKIPPED).exists()
def test_fetch_source_is_blocked_by_expired_review(db):
source = Source.objects.create(
name="Expired review source",
source_type=Source.Type.EMPLOYER,
base_url="https://jobs.example.org/vacatures/",
domain="jobs.example.org",
status=Source.Status.ACTIVE,
policy=Source.Policy.ALLOW,
)
SourcePolicyReview.objects.create(
source=source,
decision=SourcePolicyReview.Decision.ALLOW,
reason="Review verlopen",
expires_at=timezone.now() - timedelta(days=1),
)
result = fetch_source(source.pk)
assert result["status"] == "skipped"
assert SourceRun.objects.filter(source=source, status=SourceRun.Status.SKIPPED).exists()
# Outside the digest window this is a safe no-op.
assert build_due_digests()["sent"] == 0
@pytest.mark.integration
@pytest.mark.django_db
def test_job_profile_source_and_application_views(client, user, profile, job, source):
client.force_login(user)
assert client.get(reverse("jobs:list"), {"q": "Infrastructure"}).status_code == 200
assert client.get(reverse("jobs:detail", kwargs={"pk": job.pk})).status_code == 200
response = client.post(
reverse("jobs:feedback", kwargs={"pk": job.pk}),
{"action": Feedback.Action.INTERESTING},
)
assert response.status_code == 302
assert Feedback.objects.filter(user=user, job=job).exists()
assert client.get(reverse("profiles:list")).status_code == 200
assert client.get(reverse("profiles:edit", kwargs={"pk": profile.pk})).status_code == 200
assert client.get(reverse("sources:list")).status_code == 200
assert client.get(reverse("dashboard:system")).status_code == 200
assert client.get(reverse("jobs:applications")).status_code == 200
source.policy = Source.Policy.DENY
source.save(update_fields=["policy"])
response = client.post(reverse("sources:retry", kwargs={"pk": source.pk}))
assert response.status_code == 302
@pytest.mark.integration
@pytest.mark.django_db
def test_sources_candidate_bulk_actions_and_evidence_view(client, user):
candidate = Source.objects.create(
name="Discoverd candidate",
source_type=Source.Type.EMPLOYER,
base_url="https://jobs.example.org/vacatures/",
domain="jobs.example.org",
status=Source.Status.CANDIDATE,
policy=Source.Policy.REVIEW,
metadata={
"discovery": [
{
"url": "https://jobs.example.org/careers",
"discovered_from": "html",
"reason": "career-link",
"confidence": 0.9,
"label": "Werken bij",
}
]
},
)
other = Source.objects.create(
name="Another source",
source_type=Source.Type.EMPLOYER,
base_url="https://other.example.org/",
domain="other.example.org",
status=Source.Status.CANDIDATE,
policy=Source.Policy.REVIEW,
)
client.force_login(user)
response = client.get(reverse("sources:list"))
assert response.status_code == 200
content = response.content.decode("utf-8")
assert "jobs.example.org/careers" in content
response = client.post(
reverse("sources:bulk"),
{"action": "approve_to_trial", "source_ids": [str(candidate.pk)]},
)
assert response.status_code == 302
candidate.refresh_from_db()
assert candidate.status == Source.Status.TRIAL
response = client.post(
reverse("sources:bulk"),
{"action": "dismiss", "source_ids": [str(other.pk)]},
)
assert response.status_code == 302
other.refresh_from_db()
assert other.status == Source.Status.DISABLED
assert other.policy == Source.Policy.DENY