Implements docs/api-contract-v2.md. Migration 002 adds follow-up fields. EchoTransport replaced by SMTP->Clipboard selection behind unchanged approval gate. Scheduler (APScheduler) daily 07:00 fetch+score, env-gated, default off. Test image installs workspace packages; build context moved to repo root. Recovered and committed by integration lead after W2 worker hit iteration limit.
137 lines
No EOL
4.2 KiB
Python
137 lines
No EOL
4.2 KiB
Python
"""Tests for v1 postings fetch endpoint (connector stubbed)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
@pytest.fixture()
|
|
def client():
|
|
from app.main import app
|
|
return TestClient(app)
|
|
|
|
|
|
class FakeRawPosting:
|
|
"""Minimal raw posting dict for testing."""
|
|
pass
|
|
|
|
|
|
def _make_fake_postings():
|
|
"""Return fake raw postings as dicts."""
|
|
return [
|
|
{
|
|
"source": "arbetsformedlingen",
|
|
"external_id": "af-001",
|
|
"url": "https://arbetsformedlingen.se/job/001",
|
|
"company": "Skane Tech",
|
|
"title": "Python Developer",
|
|
"location": "Malmo",
|
|
"description": "Great Python job in Malmo.",
|
|
"raw": {"id": "af-001"},
|
|
},
|
|
{
|
|
"source": "arbetsformedlingen",
|
|
"external_id": "af-002",
|
|
"url": "https://arbetsformedlingen.se/job/002",
|
|
"company": "Lund Systems",
|
|
"title": "Backend Engineer",
|
|
"location": "Lund",
|
|
"description": "Backend engineer at Lund.",
|
|
"raw": {"id": "af-002"},
|
|
},
|
|
]
|
|
|
|
|
|
class FakeConnector:
|
|
"""Fake connector that returns predefined postings."""
|
|
def fetch(self, query):
|
|
# Accept both SearchQuery objects and dicts
|
|
return _make_fake_postings()
|
|
|
|
|
|
class TestPostingsFetch:
|
|
def test_fetch_disabled_503(self, client, monkeypatch):
|
|
"""CONNECTORS_ENABLED=false -> 503."""
|
|
monkeypatch.setenv("CONNECTORS_ENABLED", "false")
|
|
resp = client.post(
|
|
"/api/postings/fetch",
|
|
json={"query": "python developer"},
|
|
)
|
|
assert resp.status_code == 503
|
|
assert "connectors_disabled" in resp.text
|
|
|
|
def test_fetch_enabled_with_stubbed_connector(self, client, monkeypatch):
|
|
"""Fetch with monkeypatched connector creates new applications."""
|
|
monkeypatch.setenv("CONNECTORS_ENABLED", "true")
|
|
# Monkeypatch the connector lookup
|
|
import app.main as main_mod
|
|
monkeypatch.setattr(main_mod, "CONNECTORS_AVAILABLE", True)
|
|
monkeypatch.setattr(
|
|
main_mod,
|
|
"_get_arbetsformedlingen_connector",
|
|
lambda: FakeConnector(),
|
|
)
|
|
|
|
resp = client.post(
|
|
"/api/postings/fetch",
|
|
json={"query": "python developer", "region": "Skane lan"},
|
|
)
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["new"] == 2
|
|
assert data["dupes"] == 0
|
|
|
|
def test_fetch_dedupes_existing_postings(self, client, monkeypatch):
|
|
"""Second fetch of same postings counts as dupes."""
|
|
monkeypatch.setenv("CONNECTORS_ENABLED", "true")
|
|
import app.main as main_mod
|
|
monkeypatch.setattr(main_mod, "CONNECTORS_AVAILABLE", True)
|
|
monkeypatch.setattr(
|
|
main_mod,
|
|
"_get_arbetsformedlingen_connector",
|
|
lambda: FakeConnector(),
|
|
)
|
|
|
|
# First fetch
|
|
resp1 = client.post(
|
|
"/api/postings/fetch",
|
|
json={"query": "python"},
|
|
)
|
|
assert resp1.status_code == 200
|
|
assert resp1.json()["new"] == 2
|
|
|
|
# Second fetch: same postings should be dupes
|
|
resp2 = client.post(
|
|
"/api/postings/fetch",
|
|
json={"query": "python"},
|
|
)
|
|
assert resp2.status_code == 200
|
|
assert resp2.json()["new"] == 0
|
|
assert resp2.json()["dupes"] == 2
|
|
|
|
def test_fetch_empty_results(self, client, monkeypatch):
|
|
"""Connector returning no postings -> new=0, dupes=0."""
|
|
monkeypatch.setenv("CONNECTORS_ENABLED", "true")
|
|
import app.main as main_mod
|
|
monkeypatch.setattr(main_mod, "CONNECTORS_AVAILABLE", True)
|
|
|
|
class EmptyConnector:
|
|
def fetch(self, query):
|
|
return []
|
|
|
|
monkeypatch.setattr(
|
|
main_mod,
|
|
"_get_arbetsformedlingen_connector",
|
|
lambda: EmptyConnector(),
|
|
)
|
|
|
|
resp = client.post(
|
|
"/api/postings/fetch",
|
|
json={"query": "rare keyword"},
|
|
)
|
|
assert resp.status_code == 200
|
|
assert resp.json()["new"] == 0
|
|
assert resp.json()["dupes"] == 0 |