Team Ai
Apppublic

booleanbeyond/jobfetch

sourceHugging Faceupdated 2mo agoView on Hugging Face
0likes
test_api.py106 linesDownload Raw Back to tests
1"""HTTP contract tests."""2 3from __future__ import annotations4 5import pytest6from fastapi.testclient import TestClient7 8from app.cache import RateLimiter9from app.main import app10 11client = TestClient(app)12 13 14def test_index_serves_the_ui():15    r = client.get("/")16    assert r.status_code == 20017    assert "JobFetch" in r.text18    assert 'id="url"' in r.text19 20 21def test_healthz_reports_subsystems():22    r = client.get("/healthz")23    assert r.status_code == 20024    body = r.json()25    assert body["ok"] is True26    assert "browser" in body and "llm" in body and "cache" in body27 28 29def test_config_endpoint_never_leaks_the_api_key():30    body = client.get("/api/config").json()31    assert "anthropic_api_key" not in body32    assert body["anthropic_api_key_present"] in (True, False)33    assert not any("sk-" in str(v) for v in body.values())34 35 36@pytest.mark.parametrize(37    "url",38    # The fixture server lives on 127.0.0.1, so this suite runs with the39    # ALLOW_PRIVATE_HOSTS escape hatch on. Strict-mode rejection of private40    # ranges is covered in test_security.py.41    ["file:///etc/passwd", "http://169.254.169.254/", "http://metadata.google.internal/",42     "notaurl::", "", "ftp://example.com/x"],43)44def test_unsafe_urls_are_rejected_with_400(url):45    r = client.post("/api/scrape", json={"url": url})46    assert r.status_code in (400, 422)47    if r.status_code == 400:48        body = r.json()49        assert body["ok"] is False50        assert body["error"] == "invalid_url"51        assert body["trace_id"]52 53 54def test_successful_scrape_response_shape(server):55    r = client.post(56        "/api/scrape",57        json={"url": f"{server}/footer_trap.html", "use_browser": False, "use_llm": False},58    )59    assert r.status_code == 20060    body = r.json()61 62    assert body["ok"] is True63    assert body["job_count"] == 664    assert body["company"] == "Acme Robotics"65    assert 0 < body["confidence"] <= 166    assert body["diagnostics"]["strategy"] == "dom_heuristic"67    assert body["diagnostics"]["trace_id"]68    assert r.headers["X-Trace-Id"]69    assert r.headers["X-Content-Type-Options"] == "nosniff"70 71    # Every job carries the full key set, so consumers never need .get() guards.72    expected = {73        "id", "requisition_id", "title", "department", "team", "location", "locations",74        "workplace_type", "employment_type", "seniority", "experience", "salary", "posted_at",75        "updated_at", "apply_url", "source", "confidence",76    }77    for job in body["jobs"]:78        assert set(job) == expected79        assert isinstance(job["title"], str) and job["title"].strip()80        assert isinstance(job["locations"], list)81 82 83def test_get_endpoint_matches_post(server):84    a = client.get("/api/scrape", params={"url": f"{server}/table.html",85                                          "use_browser": False, "use_llm": False}).json()86    b = client.post("/api/scrape", json={"url": f"{server}/table.html",87                                         "use_browser": False, "use_llm": False}).json()88    assert a["job_count"] == b["job_count"] == 589 90 91@pytest.mark.asyncio92async def test_rate_limiter_token_bucket():93    limiter = RateLimiter(per_minute=60, burst=2)94    assert await limiter.allow("ip") is True95    assert await limiter.allow("ip") is True96    assert await limiter.allow("ip") is False97    assert await limiter.allow("other-ip") is True98 99 100@pytest.mark.asyncio101async def test_rate_limiter_key_space_is_bounded():102    limiter = RateLimiter(per_minute=60, burst=2, max_keys=10)103    for i in range(100):104        await limiter.allow(f"ip-{i}")105    assert len(limiter._buckets) <= 10106