feat: release regional radar and mailbox integrations
deploy / deploy (push) Canceled after 0s

This commit is contained in:
Jens
2026-07-22 05:12:07 +02:00
parent 598d3ec18a
commit 551d0f46c2
131 changed files with 6209 additions and 336 deletions
@@ -85,10 +85,14 @@ def _run_html_accessibility_probe(client, user, profile, job) -> None:
response = client.get(reverse("dashboard:today"))
assert response.status_code == 200
_assert_basic_html_accessibility(response.content, page_name="dashboard")
assert "Zoek door vacatures" in response.content.decode("utf-8")
response = client.get(reverse("jobs:list"), {"q": job.original_title})
assert response.status_code == 200
_assert_basic_html_accessibility(response.content, page_name="jobs-list")
jobs_body = response.content.decode("utf-8")
assert "1 vacatures gevonden" in jobs_body
assert "88%" in jobs_body
response = client.get(reverse("jobs:detail", kwargs={"pk": job.pk}))
assert response.status_code == 200
@@ -186,12 +190,12 @@ def test_vr114_browser_flow(viewport, live_server, client, tmp_path, user, profi
page.get_by_label("Wachtwoord").fill("correct-horse-battery-staple")
page.get_by_role("button", name="Aanmelden").click()
page.wait_for_url(dashboard_url)
_assert_playwright_accessibility(page, expected_h1="Vandaag")
_assert_playwright_accessibility(page, expected_h1="Goedemorgen")
# Dashboard -> filter -> detail.
page.get_by_role("link", name="Vacatures").click()
page.get_by_role("textbox", name="Zoeken").fill(job.original_title)
page.get_by_role("button", name="Filter").click()
page.get_by_role("button", name="Filters toepassen").click()
assert page.url.startswith(jobs_url)
page.get_by_role("link", name=job.original_title).first.click()
page.wait_for_url(detail_url)
@@ -209,13 +213,12 @@ def test_vr114_browser_flow(viewport, live_server, client, tmp_path, user, profi
page.wait_for_selector(".message.success")
# Profiel aanpassen.
page.get_by_role("link", name="Profiel").click()
page.get_by_role("link", name="Zoekprofiel").click()
page.wait_for_url(profile_list_url)
page.get_by_role("link", name="Bewerken").click()
page.get_by_label("Gewenste functietitels").fill(
"Infrastructure Engineer\nSysteembeheerder"
)
page.get_by_role("button", name="Profiel opslaan").click()
page.get_by_role("link", name="Profiel bewerken").click()
page.get_by_label("Infrastructure engineer").check()
page.get_by_label("System engineer").check()
page.get_by_role("button", name="Instellingen opslaan").click()
page.wait_for_url(profile_list_url)
# Sollicitatie dossier openen en bijwerken.
@@ -231,12 +234,13 @@ def test_vr114_browser_flow(viewport, live_server, client, tmp_path, user, profi
(timezone.localdate() + timedelta(days=7)).isoformat()
)
page.locator("textarea[name='notes']").fill("Eerste follow-up ingepland.")
page.get_by_role("button", name="Opslaan").click()
page.get_by_role("button", name="Dossier opslaan").click()
page.wait_for_url(applications_url)
# Handmatige import via plaktekst.
page.get_by_role("link", name="Brongezondheid").click()
page.get_by_role("link", name="Bronnen").click()
page.wait_for_url(sources_url)
page.locator(".import-disclosure > summary").click()
page.locator("textarea[name='pasted_text']").fill(
"Vacaturetitel: Infrastructure Engineer\nWerkgever: Example IT\nLocatie: Hasselt"
)
@@ -249,7 +253,7 @@ def test_vr114_browser_flow(viewport, live_server, client, tmp_path, user, profi
page.keyboard.press("Tab")
page.keyboard.press("Tab")
page.keyboard.press("Tab")
_assert_playwright_accessibility(page, expected_h1="Brongezondheid")
_assert_playwright_accessibility(page, expected_h1="Bronnen")
assert Feedback.objects.filter(user=user, job=job).count() >= 4
except Exception:
@@ -3,6 +3,7 @@ from pathlib import Path
from zoneinfo import ZoneInfo
import pytest
from cryptography.fernet import Fernet
from django.core import mail
from django.test import override_settings
@@ -10,8 +11,10 @@ from apps.jobs.models import Feedback, ScoreRun
from apps.jobs.services.scoring import score_and_save
from apps.notifications.models import DigestOutbox
from apps.notifications.services import build_digest_payload, create_daily_outbox, send_digest
from apps.sources.models import EmailMessageRecord, RawDocument
from apps.sources.models import EmailMessageRecord, MailboxConnection, RawDocument
from apps.sources.services.email_import import ingest_email, message_identity
from apps.sources.services.imap_import import poll_imap_mailbox
from apps.sources.services.mailbox_connections import encrypt_mailbox_password
@pytest.mark.django_db
@@ -27,6 +30,132 @@ def test_email_import_is_idempotent(profile):
assert len(first.links) == 2
@pytest.mark.django_db
def test_vdab_alert_has_provider_provenance_and_replays_idempotently(profile):
raw = Path("fixtures/emails/vdab_job_alert.eml").read_bytes()
first = ingest_email(raw)
second = ingest_email(raw)
assert second.pk == first.pk
assert first.processed is True
assert first.raw_document.metadata["alert_provider"] == "vdab"
assert len(first.links) == 2
assert all("vdab.be/vindeenjob/vacatures/" in link for link in first.links)
@pytest.mark.django_db
@pytest.mark.parametrize(
("platform", "fixture", "expected_domain"),
(
(MailboxConnection.Platform.LINKEDIN, "linkedin_job_alert.eml", "linkedin.com"),
(MailboxConnection.Platform.ICTJOB, "ictjob_job_alert.eml", "ictjob.be"),
(MailboxConnection.Platform.JOBAT, "jobat_job_alert.eml", "jobat.be"),
(MailboxConnection.Platform.STEPSTONE, "stepstone_job_alert.eml", "stepstone.be"),
(MailboxConnection.Platform.CAREERJET, "careerjet_job_alert.eml", "careerjet.be"),
(MailboxConnection.Platform.RANDSTAD, "randstad_job_alert.eml", "randstad.be"),
(
MailboxConnection.Platform.ROBERTHALF,
"roberthalf_job_alert.eml",
"roberthalf.com",
),
),
)
def test_jobboard_mail_import_keeps_platform_provenance_and_rejects_external_links(
profile, platform, fixture, expected_domain
):
raw = (Path("fixtures/emails") / fixture).read_bytes()
record = ingest_email(raw, platform=platform)
assert record.processed is True
assert len(record.links) == 1
assert expected_domain in record.links[0]
assert "outside.example.invalid" not in record.links[0]
assert record.raw_document.metadata["alert_provider"] == platform
assert record.raw_document.source.name == f"{platform.label} vacaturemailbox"
@pytest.mark.django_db
def test_imap_poll_is_bounded_and_one_bad_message_does_not_abort(profile):
valid = Path("fixtures/emails/vdab_job_alert.eml").read_bytes()
oversized = b"x" * (len(valid) + 1)
class FakeImapClient:
def __init__(self, host, port, timeout=None):
self.host = host
self.port = port
self.timeout = timeout
self.logged_out = False
def login(self, user, password):
return "OK", []
def select(self, mailbox, readonly=True):
assert readonly is True
return "OK", []
def uid(self, command, *args):
if command == "search":
return "OK", [b"1 2 3"]
uid = args[0]
if uid == b"1":
return "OK", [(b"RFC822", valid)]
if uid == b"2":
return "OK", [(b"RFC822", oversized)]
return "NO", []
def logout(self):
self.logged_out = True
clients = []
def factory(host, port, timeout=None):
client = FakeImapClient(host, port, timeout=timeout)
clients.append(client)
return client
key = Fernet.generate_key().decode("ascii")
with override_settings(
MAILBOX_CREDENTIAL_KEYS=[key],
IMAP_MAX_MESSAGES_PER_POLL=3,
IMAP_MAX_MESSAGE_BYTES=len(valid),
):
connection = MailboxConnection.objects.create(
user=profile.user,
platform=MailboxConnection.Platform.VDAB,
provider=MailboxConnection.Provider.CUSTOM,
custom_host="imap.example.invalid",
username="radar@example.invalid",
encrypted_password=encrypt_mailbox_password("test-only"),
)
first = poll_imap_mailbox(
connection, client_factory=factory, host_validator=lambda url: url
)
second = poll_imap_mailbox(
connection, client_factory=factory, host_validator=lambda url: url
)
assert first == {
"status": "ok",
"imported": 1,
"duplicates": 0,
"skipped_oversized": 1,
"failed": 1,
}
assert second == {
"status": "ok",
"imported": 0,
"duplicates": 1,
"skipped_oversized": 1,
"failed": 1,
}
assert all(client.logged_out for client in clients)
record = EmailMessageRecord.objects.get(mailbox_connection=connection)
assert record.raw_document.metadata["alert_provider"] == "vdab"
assert record.raw_document.metadata["mailbox_connection_id"] == connection.pk
@pytest.mark.django_db
def test_digest_payload_outbox_deduplication_and_send(profile, job):
score = score_and_save(job, profile)
@@ -0,0 +1,205 @@
from datetime import timedelta
import pytest
from cryptography.fernet import Fernet
from django.contrib.auth import get_user_model
from django.test import Client, override_settings
from django.urls import reverse
from django.utils import timezone
from apps.sources.models import MailboxConnection
from apps.sources.services.mailbox_connections import (
claim_mailbox_connection,
decrypt_mailbox_password,
)
from apps.sources.tasks import poll_mailbox, schedule_mailbox_polls
def _payload(**overrides):
payload = {
"platform": MailboxConnection.Platform.VDAB,
"provider": MailboxConnection.Provider.GMAIL,
"custom_host": "",
"port": 993,
"username": "vdab-alerts@example.invalid",
"password": "test-app-password",
"mailbox": "INBOX",
"poll_interval_minutes": 15,
"enabled": "on",
}
payload.update(overrides)
return payload
@pytest.mark.integration
@pytest.mark.django_db
def test_user_can_link_edit_pause_and_sync_separate_platform_mailboxes(client, user, monkeypatch):
key = Fernet.generate_key().decode("ascii")
calls = []
monkeypatch.setattr(
"apps.sources.views.poll_mailbox.delay",
lambda connection_id, force=False: calls.append((connection_id, force)),
)
client.force_login(user)
with override_settings(MAILBOX_CREDENTIAL_KEYS=[key]):
response = client.post(reverse("sources:mailbox_add"), _payload())
assert response.status_code == 302
vdab = MailboxConnection.objects.get(user=user, platform="vdab")
assert decrypt_mailbox_password(vdab) == "test-app-password"
assert "test-app-password" not in vdab.encrypted_password
response = client.post(
reverse("sources:mailbox_add"),
_payload(
platform=MailboxConnection.Platform.INDEED,
provider=MailboxConnection.Provider.OUTLOOK,
username="indeed-alerts@example.invalid",
password="second-app-password",
poll_interval_minutes=30,
),
)
assert response.status_code == 302
indeed = MailboxConnection.objects.get(user=user, platform="indeed")
edit_response = client.get(reverse("sources:mailbox_edit", args=[vdab.pk]))
assert edit_response.status_code == 200
assert b"test-app-password" not in edit_response.content
assert vdab.encrypted_password.encode() not in edit_response.content
original_ciphertext = vdab.encrypted_password
response = client.post(
reverse("sources:mailbox_edit", args=[vdab.pk]),
_payload(
username="vdab-updated@example.invalid",
password="",
poll_interval_minutes=5,
),
)
assert response.status_code == 302
vdab.refresh_from_db()
assert vdab.username == "vdab-updated@example.invalid"
assert vdab.poll_interval_minutes == 5
assert vdab.encrypted_password == original_ciphertext
assert decrypt_mailbox_password(vdab) == "test-app-password"
response = client.post(reverse("sources:mailbox_toggle", args=[indeed.pk]))
assert response.status_code == 302
indeed.refresh_from_db()
assert indeed.enabled is False
response = client.post(reverse("sources:sync_platform_alerts", args=[vdab.pk]))
assert response.status_code == 302
assert calls == [
(vdab.pk, True),
(indeed.pk, True),
(vdab.pk, True),
(vdab.pk, True),
]
@pytest.mark.integration
@pytest.mark.django_db
def test_sources_page_offers_all_reviewed_jobboard_mailboxes(client, user):
client.force_login(user)
response = client.get(reverse("sources:list"))
assert response.status_code == 200
for platform in MailboxConnection.Platform:
assert f'value="{platform.value}"'.encode() in response.content
assert f"{platform.label}-alert instellen".encode() in response.content
@pytest.mark.integration
@pytest.mark.django_db
def test_mailbox_views_are_user_scoped_and_require_encryption_key(client, user, monkeypatch):
other = get_user_model().objects.create_user(username="other", password="test-only-password")
other_connection = MailboxConnection.objects.create(
user=other,
platform=MailboxConnection.Platform.VDAB,
provider=MailboxConnection.Provider.GMAIL,
username="private@example.invalid",
encrypted_password="opaque-ciphertext",
)
client.force_login(user)
monkeypatch.setattr("apps.sources.views.poll_mailbox.delay", lambda *args, **kwargs: None)
assert (
client.get(reverse("sources:mailbox_edit", args=[other_connection.pk])).status_code == 404
)
assert (
client.post(reverse("sources:sync_platform_alerts", args=[other_connection.pk])).status_code
== 404
)
assert (
client.post(reverse("sources:mailbox_toggle", args=[other_connection.pk])).status_code
== 404
)
with override_settings(MAILBOX_CREDENTIAL_KEYS=[]):
response = client.post(reverse("sources:mailbox_add"), _payload(password="visible-once"))
assert response.status_code == 200
assert MailboxConnection.objects.filter(user=user).count() == 0
assert b"visible-once" not in response.content
assert b"Credentialopslag is niet geconfigureerd" in response.content
anonymous_response = Client().post(reverse("sources:mailbox_add"), _payload())
assert anonymous_response.status_code == 302
csrf_client = Client(enforce_csrf_checks=True)
csrf_client.force_login(user)
assert csrf_client.post(reverse("sources:mailbox_add"), _payload()).status_code == 403
@pytest.mark.integration
@pytest.mark.django_db
def test_scheduler_queues_only_due_active_mailboxes_and_claim_is_exclusive(user, monkeypatch):
now = timezone.now()
due = MailboxConnection.objects.create(
user=user,
platform=MailboxConnection.Platform.VDAB,
provider=MailboxConnection.Provider.GMAIL,
username="due@example.invalid",
encrypted_password="opaque",
next_poll_at=now - timedelta(minutes=1),
)
MailboxConnection.objects.create(
user=user,
platform=MailboxConnection.Platform.INDEED,
provider=MailboxConnection.Provider.OUTLOOK,
username="later@example.invalid",
encrypted_password="opaque",
next_poll_at=now + timedelta(minutes=20),
)
calls = []
monkeypatch.setattr(
"apps.sources.tasks.poll_mailbox.delay", lambda connection_id: calls.append(connection_id)
)
assert schedule_mailbox_polls() == {"scheduled": 1}
assert calls == [due.pk]
assert claim_mailbox_connection(connection_id=due.pk, worker_token="worker-a") is not None
assert claim_mailbox_connection(connection_id=due.pk, worker_token="worker-b") is None
@pytest.mark.integration
@pytest.mark.django_db
def test_mailbox_task_does_not_persist_or_return_secret_on_imap_error(user, monkeypatch):
connection = MailboxConnection.objects.create(
user=user,
platform=MailboxConnection.Platform.VDAB,
provider=MailboxConnection.Provider.GMAIL,
username="errors@example.invalid",
encrypted_password="opaque",
next_poll_at=timezone.now() - timedelta(minutes=1),
)
monkeypatch.setattr(
"apps.sources.tasks.poll_imap_mailbox",
lambda connection: (_ for _ in ()).throw(RuntimeError("leaked-app-password")),
)
result = poll_mailbox(connection.pk)
connection.refresh_from_db()
assert "leaked-app-password" not in str(result)
assert "leaked-app-password" not in connection.last_error_message
assert result == {"status": "failed", "error_category": "imap", "imported": 0}
+34
View File
@@ -34,6 +34,40 @@ def test_pipeline_is_idempotent_and_scores(source, profile):
assert ScoreRun.objects.filter(profile=profile).count() == 2
@pytest.mark.django_db
def test_pipeline_uses_reviewed_employer_identity_for_public_ats_feed(profile):
source = Source.objects.create(
name="Deliverect",
source_type=Source.Type.ATS,
base_url="https://api.lever.co/v0/postings/deliverect?mode=json",
domain="api.lever.co",
status=Source.Status.ACTIVE,
policy=Source.Policy.ALLOW,
metadata={"employer_name": "Deliverect", "direct_employer": True},
)
content = Path("fixtures/ats/lever-public-postings.json").read_text(encoding="utf-8")
document = RawDocument.objects.create(
source=source,
url=source.base_url,
final_url=source.base_url,
kind=RawDocument.Kind.JSON,
content_type="application/json",
content_hash=hashlib.sha256(content.encode()).hexdigest(),
body_text=content,
byte_length=len(content.encode()),
retain_until=timezone.now() + timedelta(days=7),
)
result = process_raw_document(document)
assert result["created"] == 1
job = JobPosting.objects.get()
assert job.employer_name == "Deliverect"
assert job.direct_employer is True
evidence = job.source_aliases.get().field_evidence.get(field_name="employer_name")
assert evidence.extraction_method == "reviewed-source-config"
@pytest.mark.django_db
def test_pipeline_resolves_recruiter_alias_to_direct_employer():
source = Source.objects.create(
@@ -0,0 +1,106 @@
from decimal import Decimal
import pytest
from django.urls import reverse
from apps.jobs.models import GeocodeLocationLookup
from apps.profiles.forms import SearchProfileForm
def _profile_post_data(**overrides):
data = {
"name": "Testprofiel",
"is_active": "on",
"home_postal_code": "3500",
"max_distance_km": "45",
"desired_titles": ["infrastructure engineer", "cloud engineer"],
"excluded_titles": ["sales"],
"desired_skills": ["azure", "terraform"],
"excluded_skills": ["cold calling"],
"allowed_employment_types": ["full_time", "permanent"],
"preferred_workplace": ["hybrid"],
"preferred_regions": ["Limburg"],
"excluded_regions": ["West-Vlaanderen"],
"recommendation_threshold": "65",
"top_match_threshold": "90",
"digest_time": "07:30",
}
data.update(overrides)
return data
@pytest.mark.django_db
def test_profile_form_uses_guided_choices_and_preserves_existing_custom_values(profile):
form = SearchProfileForm(instance=profile)
assert form.fields["home_postal_code"].widget.attrs["maxlength"] == 4
assert "home_municipality" not in form.fields
assert "home_latitude" not in form.fields
assert "home_longitude" not in form.fields
assert form.fields["desired_titles"].widget.allow_multiple_selected is True
assert ("systeembeheerder", "systeembeheerder") in form.fields["desired_titles"].choices
assert ("VMware", "VMware") in form.fields["desired_skills"].choices
@pytest.mark.django_db
def test_profile_form_rejects_non_belgian_postcode_and_conflicting_choices(profile):
invalid_postcode = SearchProfileForm(
data=_profile_post_data(home_postal_code="3500 Hasselt"), instance=profile
)
assert invalid_postcode.is_valid() is False
assert "home_postal_code" in invalid_postcode.errors
conflicting = SearchProfileForm(
data=_profile_post_data(preferred_regions=["Limburg"], excluded_regions=["Limburg"]),
instance=profile,
)
assert conflicting.is_valid() is False
assert "excluded_regions" in conflicting.errors
@pytest.mark.django_db
def test_profile_update_derives_home_location_from_postcode(client, user, profile):
GeocodeLocationLookup.objects.create(
source_name="test-geodata",
source_version="2026-01",
query_kind=GeocodeLocationLookup.QueryKind.POSTAL,
query_value="3500",
postal_code="3500",
municipality="Hasselt",
region="Limburg",
latitude=Decimal("50.930700"),
longitude=Decimal("5.332500"),
confidence=Decimal("1.00"),
)
client.force_login(user)
response = client.post(
reverse("profiles:edit", kwargs={"pk": profile.pk}), _profile_post_data()
)
assert response.status_code == 302
profile.refresh_from_db()
assert profile.home_postal_code == "3500"
assert profile.home_municipality == "Hasselt"
assert profile.home_latitude == Decimal("50.930700")
assert profile.desired_titles == ["infrastructure engineer", "cloud engineer"]
assert profile.preferred_workplace == ["hybrid"]
@pytest.mark.django_db
def test_profile_update_clears_stale_coordinates_when_postcode_is_unknown(client, user, profile):
client.force_login(user)
response = client.post(
reverse("profiles:edit", kwargs={"pk": profile.pk}),
_profile_post_data(home_postal_code="9999"),
follow=True,
)
assert response.status_code == 200
assert "De postcode is bewaard" in response.content.decode("utf-8")
profile.refresh_from_db()
assert profile.home_postal_code == "9999"
assert profile.home_municipality == ""
assert profile.home_latitude is None
assert profile.home_longitude is None
+91
View File
@@ -0,0 +1,91 @@
from datetime import timedelta
import pytest
import yaml
from django.core.management import call_command
from django.utils import timezone
from apps.sources.models import Source, SourcePolicyReview
from apps.sources.views import _source_list_queryset
@pytest.mark.django_db
def test_seed_sources_registers_reviewed_live_feed_and_hides_demo(tmp_path):
Source.objects.create(
name="Voorbeeldwerkgever (fixture)",
source_type=Source.Type.EMPLOYER,
base_url="https://jobs.example.org/vacatures/",
domain="jobs.example.org",
)
expires_at = (timezone.now() + timedelta(days=30)).isoformat()
retired_source = Source.objects.create(
name="Brusselse werkgever",
source_type=Source.Type.ATS,
base_url="https://brussels-employer.example/jobs",
domain="brussels-employer.example",
status=Source.Status.ACTIVE,
policy=Source.Policy.ALLOW,
next_run_at=timezone.now(),
)
payload = {
"disable_demo_sources": True,
"retire_source_urls": [retired_source.base_url],
"sources": [
{
"name": "VITO",
"url": "https://vito.recruitee.com/api/offers/",
"type": "ats",
"status": "trial",
"policy": "review",
"parser": "ats-recruitee",
"allow_public_endpoint": True,
"metadata": {"country_scope": "BE"},
"review": {
"decision": "trial",
"reason": "Publieke feed",
"evidence_link": "https://docs.recruitee.com/reference/offers",
"expires_at": expires_at,
},
},
{
"name": "Lever werkgever A",
"url": "https://api.lever.co/v0/postings/werkgever-a?mode=json",
"type": "ats",
"status": "trial",
"policy": "allow",
"parser": "ats-lever",
"allow_public_endpoint": True,
},
{
"name": "Lever werkgever B",
"url": "https://api.lever.co/v0/postings/werkgever-b?mode=json",
"type": "ats",
"status": "trial",
"policy": "allow",
"parser": "ats-lever",
"allow_public_endpoint": True,
},
],
}
seed_path = tmp_path / "sources.yaml"
seed_path.write_text(yaml.safe_dump(payload), encoding="utf-8")
call_command("seed_sources", str(seed_path))
call_command("seed_sources", str(seed_path))
demo = Source.objects.get(domain="jobs.example.org")
assert demo.status == Source.Status.DISABLED
assert demo.metadata["demo"] is True
retired_source.refresh_from_db()
assert retired_source.status == Source.Status.DISABLED
assert retired_source.next_run_at is None
assert retired_source.metadata["hidden_from_source_list"] is True
assert retired_source.metadata["retired_reason"] == "Buiten de ingestelde regionale dekking"
source = Source.objects.get(name="VITO")
assert retired_source not in _source_list_queryset()
assert source in _source_list_queryset()
assert source.allow_public_endpoint is True
assert source.parser_key == "ats-recruitee"
assert source.latest_policy_review.decision == SourcePolicyReview.Decision.TRIAL
assert source.policy_reviews.count() == 1
assert Source.objects.filter(domain="api.lever.co", source_type=Source.Type.ATS).count() == 2
+48 -1
View File
@@ -177,7 +177,20 @@ def test_job_profile_source_and_application_views(client, user, profile, job, so
@pytest.mark.integration
@pytest.mark.django_db
def test_sources_candidate_bulk_actions_and_evidence_view(client, user):
def test_sources_candidate_bulk_actions_and_evidence_view(client, user, profile):
profile.home_postal_code = "2400"
profile.max_distance_km = 40
profile.home_latitude = None
profile.home_longitude = None
profile.save(
update_fields=[
"home_postal_code",
"max_distance_km",
"home_latitude",
"home_longitude",
"updated_at",
]
)
candidate = Source.objects.create(
name="Discoverd candidate",
source_type=Source.Type.EMPLOYER,
@@ -206,12 +219,37 @@ def test_sources_candidate_bulk_actions_and_evidence_view(client, user):
status=Source.Status.CANDIDATE,
policy=Source.Policy.REVIEW,
)
watchlist = Source.objects.create(
name="Interessante werkgever zonder stabiele feed",
source_type=Source.Type.EMPLOYER,
base_url="https://watch.example.org/jobs/",
domain="watch.example.org",
status=Source.Status.PAUSED,
policy=Source.Policy.REVIEW,
metadata={
"watchlist": True,
"municipality_scope": "Olen",
"watchlist_reason": "Regionaal interessante werkgever",
"current_observation": "Momenteel geen lokale vacature bevestigd.",
"public_job_page": "https://watch.example.org/jobs/",
},
)
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
assert "Platformmailboxen" in content
assert "Mailbox koppelen en testen" in content
assert "elke vijf minuten" in content
assert "2400 Mol" in content
assert "maximaal 40 km" in content
assert "Coördinaten ontbreken nog" in content
assert "Interessante werkgeverswaaklijst" in content
assert watchlist.name in content
assert response.context["watchlist_count"] == 1
assert {item.pk for item in response.context["sources"]} == {candidate.pk, other.pk}
response = client.post(
reverse("sources:bulk"),
@@ -229,3 +267,12 @@ def test_sources_candidate_bulk_actions_and_evidence_view(client, user):
other.refresh_from_db()
assert other.status == Source.Status.DISABLED
assert other.policy == Source.Policy.DENY
response = client.post(
reverse("sources:bulk"),
{"action": "dismiss", "source_ids": [str(watchlist.pk)]},
)
assert response.status_code == 302
watchlist.refresh_from_db()
assert watchlist.status == Source.Status.PAUSED
assert watchlist.policy == Source.Policy.REVIEW
+12 -2
View File
@@ -9,10 +9,20 @@ from apps.sources.services.robots import RobotsDecision
def test_platform_domains_are_denied_including_subdomains():
assert is_denied_domain("www.linkedin.com")
assert is_denied_domain("be.indeed.com")
for domain in (
"www.linkedin.com",
"be.indeed.com",
"www.ictjob.be",
"www.jobat.be",
"www.stepstone.be",
"www.careerjet.be",
"jobs.randstad.be",
"www.roberthalf.com",
):
assert is_denied_domain(domain)
assert not is_denied_domain("jobs.example.org")
assert not assess_url("https://www.linkedin.com/jobs/view/123").allowed
assert not assess_url("https://www.ictjob.be/nl/it-vacatures-zoeken/example").allowed
@pytest.mark.security
+58
View File
@@ -92,3 +92,61 @@ def test_robots_cache_refreshes_after_ttl(db, settings, monkeypatch):
assert calls["count"] == 2
assert SourceRobotsCache.objects.filter(origin__contains="jobs.example.org").exists()
client.close()
@pytest.mark.security
def test_robots_follows_and_revalidates_redirects(db, monkeypatch):
validated_urls = []
def validate(url, **kwargs):
validated_urls.append(url)
if "127.0.0.1" in url:
from apps.sources.services.url_security import UnsafeUrlError
raise UnsafeUrlError("Privéadres")
return ValidatedUrl(
url=url,
hostname="jobs.example.org",
port=443,
addresses=("93.184.216.34",),
)
monkeypatch.setattr("apps.sources.services.robots.validate_public_url", validate)
source = Source.objects.create(
name="Redirect jobs",
source_type=Source.Type.ATS,
base_url="https://ats.example.org/jobs",
domain="ats.example.org",
status=Source.Status.ACTIVE,
policy=Source.Policy.ALLOW,
)
def safe_handler(request: httpx.Request) -> httpx.Response:
if request.url.host == "ats.example.org":
return httpx.Response(
302,
headers={"location": "https://jobs.example.org/robots.txt"},
request=request,
)
return httpx.Response(200, text="User-agent: *\nAllow: /", request=request)
client = httpx.Client(transport=httpx.MockTransport(safe_handler))
assert assess_robots(source.base_url, source=source, client=client).allowed is True
client.close()
assert validated_urls == [
"https://ats.example.org/robots.txt",
"https://jobs.example.org/robots.txt",
]
SourceRobotsCache.objects.all().delete()
def unsafe_handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(
302, headers={"location": "http://127.0.0.1/robots.txt"}, request=request
)
client = httpx.Client(transport=httpx.MockTransport(unsafe_handler))
blocked = assess_robots(source.base_url, source=source, client=client)
client.close()
assert blocked.allowed is False
assert "Privéadres" in blocked.reason
+51
View File
@@ -89,6 +89,22 @@ def test_lever_adapter_detail_supports_changed_markup():
assert job.location_text == "Aalst"
def test_lever_adapter_supports_documented_public_postings_api_shape():
adapter = LeverAdapter()
result = adapter.extract(
_load_fixture("fixtures/ats/lever-public-postings.json"),
url="https://api.lever.co/v0/postings/deliverect?mode=json",
)
assert len(result.jobs) == 1
job = result.jobs[0]
assert job.title == "Backend Engineer"
assert job.url == "https://jobs.lever.co/deliverect/deliverect-open-1"
assert job.location_text == "Ghent, Flemish Region"
assert job.employment_types == ["Full-time Permanent"]
assert job.workplace_type == "hybrid"
def test_recruitee_adapter_listing_extracts_open_jobs_and_skips_closed():
adapter = RecruiteeAdapter()
result = adapter.extract(
@@ -114,6 +130,23 @@ def test_recruitee_adapter_detail_supports_changed_markup():
assert job.employment_types == ["permanent"]
def test_recruitee_adapter_supports_documented_public_offers_shape():
adapter = RecruiteeAdapter()
result = adapter.extract(
_load_fixture("fixtures/ats/recruitee-public-offers.json"),
url="https://vito.recruitee.com/api/offers/",
)
assert len(result.jobs) == 1
job = result.jobs[0]
assert job.title == "Research Software Engineer"
assert job.employer_name == "VITO"
assert job.url == "https://jobs.vito.be/o/research-software-engineer"
assert job.postal_code == "2400"
assert job.employment_types == ["fulltime_fixed_term"]
assert job.workplace_type == "hybrid"
def test_smartrecruiters_adapter_listing_extracts_open_jobs_and_skips_closed():
adapter = SmartRecruitersAdapter()
result = adapter.extract(
@@ -139,6 +172,24 @@ def test_smartrecruiters_adapter_detail_supports_changed_markup():
assert job.workplace_type in {"remote", "hybrid", ""}
def test_smartrecruiters_adapter_supports_documented_public_postings_shape():
adapter = SmartRecruitersAdapter()
result = adapter.extract(
_load_fixture("fixtures/ats/smartrecruiters-public-postings.json"),
url=("https://api.smartrecruiters.com/v1/companies/ARHS/postings?limit=100&country=be"),
)
assert len(result.jobs) == 2
job = result.jobs[1]
assert job.title == "Cloud Engineer"
assert job.employer_name == "ARHS"
assert job.url == "https://jobs.smartrecruiters.com/ARHS/744000136173889"
assert job.location_text == "Leuven"
assert job.country == "be"
assert job.employment_types == ["Permanent"]
assert job.workplace_type == "remote"
def test_workable_adapter_listing_extracts_open_jobs_and_skips_closed():
adapter = WorkableAdapter()
result = adapter.extract(
@@ -24,3 +24,18 @@ def test_runtime_is_compatible_with_read_only_container() -> None:
assert '"--no-control-socket"' in dockerfile
assert "exec -T web python manage.py collectstatic" not in deploy_script
assert "for required_host in 127.0.0.1 localhost" in deploy_script
def test_deployment_examples_use_per_platform_mailbox_configuration() -> None:
required_keys = {
"MAILBOX_CREDENTIAL_KEYS",
"IMAP_CONNECT_TIMEOUT_SECONDS",
"IMAP_MAX_MESSAGES_PER_POLL",
"IMAP_MAX_MESSAGE_BYTES",
}
for filename in (".env.example", "scripts/codex_bootstrap.sh", "scripts/release_verify.sh"):
content = (ROOT / filename).read_text(encoding="utf-8")
assert "IMAP_ENABLED" not in content
for key in required_keys:
assert f"{key}=" in content
+44
View File
@@ -1,6 +1,10 @@
from pathlib import Path
import pytest
from apps.sources.adapters.email_alert import EmailAlertAdapter
from apps.sources.models import MailboxConnection
from apps.sources.platforms import PLATFORM_ALERTS
def test_email_adapter_extracts_job_links_and_skips_unsubscribe():
@@ -10,3 +14,43 @@ def test_email_adapter_extracts_job_links_and_skips_unsubscribe():
assert "https://jobs.example.org/vacatures/infrastructure-engineer" in urls
assert "https://www.linkedin.com/jobs/view/123456" in urls
assert all("unsubscribe" not in url for url in urls)
def test_email_adapter_recognizes_sanitized_vdab_alert():
raw = Path("fixtures/emails/vdab_job_alert.eml").read_bytes()
result = EmailAlertAdapter().extract_message(raw)
assert {job.title for job in result.jobs} == {"Python developer", "Cloud engineer"}
assert all(job.raw["alert_provider"] == "vdab" for job in result.jobs)
assert all(job.raw["target_domain"] == "www.vdab.be" for job in result.jobs)
assert all("uitschrijven" not in job.url for job in result.jobs)
@pytest.mark.parametrize(
("provider", "fixture", "title", "domain"),
(
("linkedin", "linkedin_job_alert.eml", "Software Engineer bij Example", "linkedin.com"),
("ictjob", "ictjob_job_alert.eml", "Cloud Engineer", "ictjob.be"),
("jobat", "jobat_job_alert.eml", "Data Engineer", "jobat.be"),
("stepstone", "stepstone_job_alert.eml", "Software Developer", "stepstone.be"),
("careerjet", "careerjet_job_alert.eml", "System Engineer", "careerjet.be"),
("randstad", "randstad_job_alert.eml", "Infrastructure Engineer", "randstad.be"),
("roberthalf", "roberthalf_job_alert.eml", "Network Engineer", "roberthalf.com"),
),
)
def test_platform_bound_alert_extracts_only_expected_https_jobboard_link(
provider, fixture, title, domain
):
raw = (Path("fixtures/emails") / fixture).read_bytes()
result = EmailAlertAdapter().extract_message(raw, expected_provider=provider)
assert [job.title for job in result.jobs] == [title]
assert result.jobs[0].raw["alert_provider"] == provider
assert result.jobs[0].raw["target_domain"].endswith(domain)
assert result.jobs[0].url.startswith("https://")
assert "outside.example.invalid" not in result.jobs[0].url
def test_mailbox_platform_choices_and_alert_catalog_stay_in_sync():
assert set(MailboxConnection.Platform.values) == set(PLATFORM_ALERTS)
+110
View File
@@ -0,0 +1,110 @@
import pytest
from cryptography.fernet import Fernet
from django.core.exceptions import ValidationError
from django.test import override_settings
from apps.sources.models import MailboxConnection
from apps.sources.services.imap_import import poll_imap_mailbox
from apps.sources.services.mailbox_connections import (
MailboxCredentialError,
decrypt_mailbox_password,
encrypt_mailbox_password,
rotate_mailbox_credentials,
)
from apps.sources.services.url_security import UnsafeUrlError
def test_mailbox_password_is_encrypted_and_supports_key_rotation():
old_key = Fernet.generate_key().decode("ascii")
new_key = Fernet.generate_key().decode("ascii")
with override_settings(MAILBOX_CREDENTIAL_KEYS=[old_key]):
ciphertext = encrypt_mailbox_password("app-password-value")
connection = MailboxConnection(encrypted_password=ciphertext)
assert "app-password-value" not in ciphertext
with override_settings(MAILBOX_CREDENTIAL_KEYS=[new_key, old_key]):
assert decrypt_mailbox_password(connection) == "app-password-value"
rotated = encrypt_mailbox_password("replacement-value")
assert Fernet(new_key.encode("ascii")).decrypt(rotated.encode("ascii")) == b"replacement-value"
def test_mailbox_password_fails_closed_without_valid_key():
with (
override_settings(MAILBOX_CREDENTIAL_KEYS=[]),
pytest.raises(MailboxCredentialError, match="niet geconfigureerd"),
):
encrypt_mailbox_password("never-stored")
with (
override_settings(MAILBOX_CREDENTIAL_KEYS=["invalid"]),
pytest.raises(MailboxCredentialError, match="ongeldige sleutel"),
):
encrypt_mailbox_password("never-stored")
@pytest.mark.parametrize("host", ["", "https://imap.example.org", "imap.example.org/path", "a b"])
def test_custom_mailbox_host_rejects_non_hostname_values(host):
connection = MailboxConnection(
platform=MailboxConnection.Platform.VDAB,
provider=MailboxConnection.Provider.CUSTOM,
custom_host=host,
port=993,
username="mailbox@example.invalid",
encrypted_password="ciphertext",
)
with pytest.raises(ValidationError):
connection.clean()
@pytest.mark.parametrize("port", [0, 65536])
def test_mailbox_rejects_invalid_port(port):
connection = MailboxConnection(
platform=MailboxConnection.Platform.VDAB,
provider=MailboxConnection.Provider.GMAIL,
port=port,
username="mailbox@example.invalid",
encrypted_password="ciphertext",
)
with pytest.raises(ValidationError):
connection.clean()
@pytest.mark.django_db
def test_rotation_reencrypts_existing_credentials_with_primary_key(user):
old_key = Fernet.generate_key().decode("ascii")
new_key = Fernet.generate_key().decode("ascii")
with override_settings(MAILBOX_CREDENTIAL_KEYS=[old_key]):
ciphertext = encrypt_mailbox_password("rotate-me")
connection = MailboxConnection.objects.create(
user=user,
platform=MailboxConnection.Platform.VDAB,
provider=MailboxConnection.Provider.GMAIL,
username="rotate@example.invalid",
encrypted_password=ciphertext,
)
with override_settings(MAILBOX_CREDENTIAL_KEYS=[new_key, old_key]):
assert rotate_mailbox_credentials() == 1
connection.refresh_from_db()
with override_settings(MAILBOX_CREDENTIAL_KEYS=[new_key]):
assert decrypt_mailbox_password(connection) == "rotate-me"
@pytest.mark.django_db
def test_private_imap_host_is_blocked_before_connection(user):
key = Fernet.generate_key().decode("ascii")
with override_settings(MAILBOX_CREDENTIAL_KEYS=[key]):
connection = MailboxConnection.objects.create(
user=user,
platform=MailboxConnection.Platform.VDAB,
provider=MailboxConnection.Provider.CUSTOM,
custom_host="127.0.0.1",
username="private@example.invalid",
encrypted_password=encrypt_mailbox_password("never-sent"),
)
with pytest.raises(UnsafeUrlError, match="Niet-publiek"):
poll_imap_mailbox(
connection,
client_factory=lambda *args, **kwargs: pytest.fail(
"Private IMAP-host had niet geconnecteerd mogen worden"
),
)
+253
View File
@@ -0,0 +1,253 @@
from pathlib import Path
import pytest
from apps.sources.adapters.kempen import KempenEmployerAdapter
from apps.sources.adapters.mol_region import MolRegionEmployerAdapter
from apps.sources.adapters.regional import CordaCampusAdapter, LocalEmployerListingAdapter
def test_corda_adapter_extracts_local_employer_cards_and_rejects_off_domain_links():
content = Path("fixtures/pages/corda-campus-jobs.html").read_text(encoding="utf-8")
result = CordaCampusAdapter().extract(
content,
url="https://www.cordacampus.com/jobs/",
)
assert result.parser_key == "regional-corda-campus"
assert len(result.jobs) == 2
assert {job.title for job in result.jobs} == {"System Engineer", "Data Engineer"}
assert {job.employer_name for job in result.jobs} == {
"Voorbeeld IT",
"Voorbeeld Data",
}
assert all(job.region == "Limburg" for job in result.jobs)
assert all("evil.example" not in job.url for job in result.jobs)
def test_corda_adapter_does_not_capture_individual_detail_pages():
result = CordaCampusAdapter().extract(
"<h1>System Engineer</h1>",
url="https://www.cordacampus.com/job/system-engineer/",
)
assert result.jobs == []
assert result.confidence == 0.0
@pytest.mark.parametrize(
("fixture_name", "url", "expected_title", "expected_employer"),
[
(
"aca-group-jobs.html",
"https://acagroup.be/en/jobs",
"AWS Cloud & DevOps Engineer",
"ACA Group",
),
(
"xplore-group-jobs.html",
"https://www.xploregroup.be/en/jobs",
"Platform Engineer",
"Xplore Group",
),
(
"uhasselt-jobs.html",
"https://www.uhasselt.be/vacatures",
"Expert systeembeheerder (100%)",
"Universiteit Hasselt",
),
(
"pxl-jobs.html",
"https://ses.pxl.be/",
"Lector netwerkbeheer",
"Hogeschool PXL",
),
(
"zol-jobs.html",
"https://ziekenhuis-oost-limburg.cvw.io/?lang=nl-BE",
"(Senior) IT Engineer Infrastructuur",
"Ziekenhuis Oost-Limburg",
),
],
)
def test_local_employer_adapter_extracts_reviewed_routes_only(
fixture_name, url, expected_title, expected_employer
):
content = Path(f"fixtures/pages/{fixture_name}").read_text(encoding="utf-8")
result = LocalEmployerListingAdapter().extract(content, url=url)
assert result.parser_key == "regional-local-employers"
assert len(result.jobs) == 1
assert result.jobs[0].title == expected_title
assert result.jobs[0].employer_name == expected_employer
assert result.jobs[0].region == "Limburg"
assert "evil.example" not in result.jobs[0].url
def test_local_employer_adapter_fails_closed_for_unreviewed_host_or_path():
adapter = LocalEmployerListingAdapter()
assert adapter.extract("<h3>Fake</h3>", url="https://evil.example/jobs").jobs == []
assert (
adapter.extract(
"<h3>Detail</h3>",
url="https://www.uhasselt.be/vacatures/detail/3999",
).jobs
== []
)
@pytest.mark.parametrize(
("fixture_name", "url", "expected_title", "expected_employer"),
[
(
"sck-cen-jobs.html",
"https://www.sckcen.be/nl/carriere/vacatures",
"Automatisatie Ingenieur PLC & OT-netwerken",
"SCK CEN",
),
(
"cipal-schaubroeck-jobs.html",
"https://cipalschaubroeck.teamtailor.com/jobs",
"Network & Security Engineer",
"Cipal Schaubroeck",
),
(
"vanroey-jobs.html",
"https://www.vanroey.be/en/job-overview/",
"Network Engineer",
"VanRoey",
),
(
"netropolix-jobs.html",
"https://netropolix.recruitee.com/",
"System Engineer - Geel",
"NTX (Netropolix)",
),
(
"thomas-more-jobs.html",
"https://jobpage.cvwarehouse.com/?companyGuid=eab9ca13-ee10-4504-8a87-a785d0b037ef&lang=nl-BE",
"Een medewerker lokale IT - Campus Geel",
"Thomas More",
),
],
)
def test_mol_region_adapter_extracts_only_reviewed_local_cards(
fixture_name, url, expected_title, expected_employer
):
content = Path(f"fixtures/pages/{fixture_name}").read_text(encoding="utf-8")
result = MolRegionEmployerAdapter().extract(content, url=url)
assert result.parser_key == "regional-mol-employers"
assert result.warnings == []
assert len(result.jobs) == 1
assert result.jobs[0].title == expected_title
assert result.jobs[0].employer_name == expected_employer
assert result.jobs[0].region == "Antwerpen"
assert "evil.example" not in result.jobs[0].url
def test_mol_region_adapter_fails_closed_for_unreviewed_route_and_company():
adapter = MolRegionEmployerAdapter()
assert adapter.extract("<h1>Fake</h1>", url="https://evil.example/jobs").jobs == []
assert (
adapter.extract(
"<h1>Detail</h1>",
url="https://www.sckcen.be/nl/carriere/vacatures/detail",
).jobs
== []
)
@pytest.mark.parametrize(
("fixture_name", "url", "expected_title", "expected_employer", "expected_postal"),
[
(
"ziekenhuis-geel-jobs.html",
"https://ziekenhuisgeel.careersite.be/nl/vacatures",
"ICT Support Engineer",
"Ziekenhuis Geel",
"2440",
),
(
"stad-geel-jobs.html",
"https://geel.hro.be/",
"Administratief medewerker digitale dienstverlening",
"Lokaal bestuur Geel",
"2440",
),
(
"stad-turnhout-jobs.html",
"https://jobs.turnhout.be/",
"Coördinator digitale projecten",
"Stad Turnhout",
"2300",
),
(
"renotec-geel-jobs.html",
"https://jobs.renotec.be/nl/alle-jobs/",
"Technisch commercieel verantwoordelijke",
"Group Renotec",
"2440",
),
(
"ravago-kempen-jobs.html",
"https://ravago.softgarden.io/en/vacancies",
"SAP Operations Analyst",
"Ravago",
"2370",
),
(
"sanofi-geel-jobs.html",
"https://jobs.sanofi.com/en/belgium",
"Maintenance Engineer",
"Sanofi",
"2440",
),
(
"daf-westerlo-jobs.html",
"https://www.daf.com/nl-nl/werken-bij-daf/vacatures",
"Technieker Facilitaire Diensten",
"DAF Trucks",
"2260",
),
],
)
def test_kempen_adapter_extracts_only_reviewed_local_cards(
fixture_name, url, expected_title, expected_employer, expected_postal
):
content = Path(f"fixtures/pages/{fixture_name}").read_text(encoding="utf-8")
result = KempenEmployerAdapter().extract(content, url=url)
assert result.parser_key == "regional-kempen-employers"
assert result.warnings == []
assert len(result.jobs) == 1
assert result.jobs[0].title == expected_title
assert result.jobs[0].employer_name == expected_employer
assert result.jobs[0].postal_code == expected_postal
assert "evil.example" not in result.jobs[0].url
def test_kempen_adapter_fails_closed_for_unreviewed_route():
adapter = KempenEmployerAdapter()
assert adapter.extract("<h1>Fake</h1>", url="https://evil.example/jobs").jobs == []
assert (
adapter.extract(
"<h1>Detail</h1>",
url="https://jobs.sanofi.com/en/search-jobs",
).jobs
== []
)
assert (
adapter.extract(
"<h1>Andere werkgever</h1>",
url="https://jobpage.cvwarehouse.com/?companyGuid=other&lang=nl-BE",
).jobs
== []
)
+14 -1
View File
@@ -3,7 +3,20 @@ from __future__ import annotations
import pytest
from django.core.exceptions import ImproperlyConfigured
from config.settings import _validate_production_security
from config.settings import _validate_production_security, env_int_range
def test_env_int_range_accepts_default_and_rejects_invalid_values(monkeypatch):
monkeypatch.delenv("TEST_BOUNDED_SETTING", raising=False)
assert env_int_range("TEST_BOUNDED_SETTING", 6, minimum=0, maximum=23) == 6
monkeypatch.setenv("TEST_BOUNDED_SETTING", "24")
with pytest.raises(ImproperlyConfigured):
env_int_range("TEST_BOUNDED_SETTING", 6, minimum=0, maximum=23)
monkeypatch.setenv("TEST_BOUNDED_SETTING", "morgen")
with pytest.raises(ImproperlyConfigured):
env_int_range("TEST_BOUNDED_SETTING", 6, minimum=0, maximum=23)
def test_validate_production_security_skips_when_debug_mode_is_enabled():
+25
View File
@@ -2,6 +2,7 @@ from datetime import timedelta
import pytest
from django.core.exceptions import ValidationError
from django.db import IntegrityError
from django.utils import timezone
from apps.sources.models import Source, SourceRun
@@ -40,3 +41,27 @@ def test_source_run_finish_updates_metrics(source):
assert run.finished_at is not None
assert run.http_status == 200
assert run.extracted_count == 3
@pytest.mark.django_db(transaction=True)
def test_shared_ats_domain_accepts_distinct_public_feeds_but_not_duplicate_urls():
Source.objects.create(
name="Lever A",
source_type=Source.Type.ATS,
base_url="https://api.lever.co/v0/postings/a?mode=json",
domain="api.lever.co",
)
Source.objects.create(
name="Lever B",
source_type=Source.Type.ATS,
base_url="https://api.lever.co/v0/postings/b?mode=json",
domain="api.lever.co",
)
with pytest.raises(IntegrityError):
Source.objects.create(
name="Lever A dubbel",
source_type=Source.Type.ATS,
base_url="https://api.lever.co/v0/postings/a?mode=json",
domain="api.lever.co",
)
+15
View File
@@ -0,0 +1,15 @@
import ssl
from pathlib import Path
import certifi
from apps.sources.services.tls import trusted_tls_context
def test_application_tls_context_uses_strict_pinned_ca_bundle():
context = trusted_tls_context()
assert Path(certifi.where()).is_file()
assert context.verify_mode == ssl.CERT_REQUIRED
assert context.check_hostname is True
assert context.cert_store_stats()["x509_ca"] > 0