Files
VacatureRadar/tests/unit/test_employer_resolution.py
T
Jens b8091e59bd
deploy / deploy (push) Canceled after 0s
Initial deploy setup
2026-07-21 14:00:00 +02:00

177 lines
6.1 KiB
Python

from __future__ import annotations
from datetime import UTC, datetime
import pytest
from apps.jobs.models import Employer, JobPosting
from apps.jobs.services.employer_resolution import resolve_direct_employer_match
from apps.jobs.services.normalization import CanonicalJobDraft
from apps.sources.models import Source
def _draft_for_test(job: JobPosting, *, **overrides) -> CanonicalJobDraft:
values = {
"source_url": "https://jobs.smartrecruiters.com/example/security-engineer",
"canonical_url": "https://jobs.smartrecruiters.com/example/security-engineer",
"external_id": "sr-security-123",
"title": job.original_title,
"normalized_title": job.normalized_title,
"job_family": job.job_family,
"employer_name": job.employer_name,
"employer_domain": "jobs.example.org",
"location_text": job.raw_location,
"region": job.region,
"municipality": job.municipality,
"postal_code": job.postal_code,
"country": job.country,
"workplace_type": job.workplace_type,
"employment_types": job.employment_types,
"language": "nl",
"description_html": "<p>Test</p>",
"description_text": "test beschrijving",
"date_posted": datetime(2026, 1, 10, tzinfo=UTC),
"valid_through": datetime(2026, 6, 1, tzinfo=UTC),
"compensation": {},
"skills_required": [],
"skills_preferred": [],
"content_hash": job.content_hash,
"canonical_key": "x" * 64,
"evidence": [],
"raw": {},
}
values.update(overrides)
return CanonicalJobDraft(**values)
@pytest.mark.django_db
def test_resolver_merges_recruiter_alias_to_direct_source():
employer = Employer.objects.create(
name="Example IT",
normalized_name="example it",
domain="jobs.example.org",
)
JobPosting.objects.create(
employer=employer,
original_title="Security Engineer",
normalized_title="security engineer",
canonical_url="https://jobs.example.org/vacatures/security-engineer",
canonical_key="a" * 64,
job_family="security",
content_hash="a" * 64,
description_text="Security engineer voor beveiliging.",
raw_location="Antwerpen",
region="Antwerpen",
municipality="Antwerpen",
workplace_type=JobPosting.Workplace.HYBRID,
employment_types=["full_time"],
direct_employer=True,
)
source = Source.objects.create(
name="SmartRecruiters",
source_type=Source.Type.ATS,
base_url="https://jobs.smartrecruiters.com/example",
domain="jobs.smartrecruiters.com",
status=Source.Status.ACTIVE,
policy=Source.Policy.ALLOW,
)
# Use an existing direct employer job as the resolver target.
target_job = JobPosting.objects.first()
decision = resolve_direct_employer_match(
_draft_for_test(target_job),
source=source,
)
assert decision.job == target_job
assert decision.reason == "resolved_direct_match"
assert decision.canonical_url == "https://jobs.example.org/vacatures/security-engineer"
@pytest.mark.django_db
def test_resolver_flags_conflicting_location_for_review():
employer = Employer.objects.create(
name="Example IT",
normalized_name="example it",
domain="jobs.example.org",
)
JobPosting.objects.create(
employer=employer,
original_title="Security Engineer",
normalized_title="security engineer",
canonical_url="https://jobs.example.org/vacatures/security-engineer",
canonical_key="b" * 64,
job_family="security",
content_hash="b" * 64,
description_text="Security engineer voor beveiliging.",
raw_location="Antwerpen",
region="Antwerpen",
municipality="Antwerpen",
workplace_type=JobPosting.Workplace.HYBRID,
employment_types=["full_time"],
direct_employer=True,
)
source = Source.objects.create(
name="SmartRecruiters",
source_type=Source.Type.ATS,
base_url="https://jobs.smartrecruiters.com/example",
domain="jobs.smartrecruiters.com",
status=Source.Status.ACTIVE,
policy=Source.Policy.ALLOW,
)
decision = resolve_direct_employer_match(
_draft_for_test(
JobPosting.objects.first(),
location_text="Gent",
municipality="Gent",
region="Oost-Vlaanderen",
),
source=source,
)
assert decision.job is None
assert decision.reason == "review_direct_conflict"
@pytest.mark.django_db
def test_resolver_keeps_candidate_unmerged_when_similarity_is_conservative():
employer = Employer.objects.create(
name="Example IT",
normalized_name="example it",
domain="jobs.example.org",
)
JobPosting.objects.create(
employer=employer,
original_title="Security Engineer",
normalized_title="security engineer",
canonical_url="https://jobs.example.org/vacatures/security-engineer",
canonical_key="c" * 64,
job_family="security",
content_hash="c" * 64,
description_text="Security engineer voor beveiliging.",
raw_location="Antwerpen",
region="Antwerpen",
municipality="Antwerpen",
workplace_type=JobPosting.Workplace.HYBRID,
employment_types=["full_time"],
direct_employer=True,
)
source = Source.objects.create(
name="StaffingBoard",
source_type=Source.Type.ATS,
base_url="https://jobs.staffingboard.com/example",
domain="jobs.staffingboard.com",
status=Source.Status.ACTIVE,
policy=Source.Policy.ALLOW,
)
decision = resolve_direct_employer_match(
_draft_for_test(
JobPosting.objects.first(),
title="Data Analyst",
normalized_title="data analyst",
location_text="Brussel",
employer_name="Another Employer",
employer_domain="jobs.staffingboard.com",
),
source=source,
)
assert decision.job is None
assert decision.reason == "no_direct_resolution"