This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
),
|
||||
)
|
||||
@@ -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
|
||||
== []
|
||||
)
|
||||
@@ -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():
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user