Skip to content

Commit 39fc02c

Browse files
authored
test files
1 parent 60be7ac commit 39fc02c

7 files changed

Lines changed: 122 additions & 0 deletions

File tree

tests/__init__.py

Whitespace-only changes.

tests/conftest.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""Test fixtures. A fresh temp SQLite DB per test session; the app lifespan
2+
trains the model and seeds the two demo resumes."""
3+
import os
4+
import tempfile
5+
6+
import pytest
7+
8+
9+
@pytest.fixture(scope="session")
10+
def client():
11+
tmpdir = tempfile.mkdtemp()
12+
os.environ["AIBF_DATABASE_URL"] = f"sqlite:///{tmpdir}/test.db"
13+
os.environ["AIBF_SEED_DEMO"] = "1"
14+
from fastapi.testclient import TestClient
15+
16+
from app.main import app
17+
18+
with TestClient(app) as c: # entering the context runs the lifespan startup
19+
yield c
20+
21+
22+
# strongly biased vs clean resume payloads reused across tests
23+
BIASED = {
24+
"skills": ["python", "sql", "aws", "docker", "ml"],
25+
"relevant_experience": 9, "certifications": 3, "projects": 4,
26+
"education_tier": 3, "employment_gap_months": 18,
27+
"age": 55, "gender": "F", "ethnicity": "black",
28+
}
29+
CLEAN = {
30+
"skills": ["python", "sql", "aws"],
31+
"relevant_experience": 6, "certifications": 2, "projects": 3,
32+
"education_tier": 2, "employment_gap_months": 0,
33+
"age": 31, "gender": "M", "ethnicity": "white",
34+
}

tests/test_aibf.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from tests.conftest import BIASED, CLEAN
2+
3+
4+
def _analyze(client, resume_id, features):
5+
sub = client.post("/api/resumes", json={"resume_id": resume_id, "features": features}).json()
6+
return client.post("/api/aibf/analyze",
7+
json={"ats_decision_id": sub["ats_decision_id"], "resume_id": resume_id}).json()
8+
9+
10+
def test_biased_resume_is_flagged_with_explanations(client):
11+
res = _analyze(client, "res_bias_1", BIASED)
12+
assert res["flagged"] is True
13+
assert res["bias_score"] > res["bias_threshold_limits"]["upper_limit"]
14+
assert len(res["explanations"]) >= 2
15+
assert "attributions" in res
16+
17+
18+
def test_clean_resume_is_not_flagged(client):
19+
res = _analyze(client, "res_clean_1", CLEAN)
20+
assert res["flagged"] is False
21+
assert res["bias_score"] <= res["bias_threshold_limits"]["upper_limit"]
22+
23+
24+
def test_admin_flagged_feed(client):
25+
_analyze(client, "res_bias_2", BIASED)
26+
feed = client.get("/api/admin/flagged").json()
27+
assert any(item["bias_score"] > 0 for item in feed)

tests/test_ats.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
def test_ats_evaluate_returns_data_points(client):
2+
client.post("/api/resumes", json={"resume_id": "res_ats_1", "candidate_id": "c1"})
3+
r = client.post("/api/ats/evaluate",
4+
json={"resume_id": "res_ats_1", "job_description": "python sql aws ml role"})
5+
assert r.status_code == 200
6+
body = r.json()
7+
assert body["ats_decision_id"].startswith("ats_")
8+
assert "contributions" in body["data_points"]
9+
assert 0 <= body["score"] <= 100

tests/test_feedback.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from tests.conftest import BIASED
2+
3+
4+
def test_submit_hr_feedback(client):
5+
sub = client.post("/api/resumes", json={"resume_id": "res_fb_1", "features": BIASED}).json()
6+
an = client.post("/api/aibf/analyze",
7+
json={"ats_decision_id": sub["ats_decision_id"], "resume_id": "res_fb_1"}).json()
8+
r = client.post("/api/admin/feedback", json={
9+
"feedback_type": "override", "hr_id": "hr_1",
10+
"ats_decision_id": sub["ats_decision_id"], "aibf_decision_id": an["aibf_decision_id"],
11+
"feedback_details": "Agree with AIBF; overriding ATS rejection.",
12+
"final_verdict": "override",
13+
})
14+
assert r.status_code == 201
15+
assert r.json()["model_training_feedback_id"].startswith("mtf_")
16+
assert r.json()["training_outcome"] == "recorded"

tests/test_resumes.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
def test_submit_resume_returns_ats_decision(client):
2+
r = client.post("/api/resumes", json={"candidate_id": "cand_x", "file_url": "s3://x"})
3+
assert r.status_code == 201
4+
body = r.json()
5+
assert body["ats_decision_id"].startswith("ats_")
6+
assert 0 <= body["score"] <= 100
7+
assert body["final_verdict"] in {"accepted", "review", "rejected"}
8+
9+
10+
def test_list_resumes_includes_seeded(client):
11+
r = client.get("/api/resumes")
12+
assert r.status_code == 200
13+
ids = {x["resume_id"] for x in r.json()}
14+
assert "res_demo_biased" in ids and "res_demo_clean" in ids
15+
16+
17+
def test_flagged_filter_returns_biased_only(client):
18+
flagged = client.get("/api/resumes", params={"status": "flagged"}).json()
19+
ids = {x["resume_id"] for x in flagged}
20+
assert "res_demo_biased" in ids
21+
assert "res_demo_clean" not in ids

tests/test_retrain.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
def test_retrain_bumps_model_version(client):
2+
before = client.get("/health").json()["model_version"]
3+
r = client.post("/api/model-training/retrain", json={"feedback_id": None})
4+
assert r.status_code == 200
5+
body = r.json()
6+
assert body["training_outcome"] == "success"
7+
assert body["model_training_feedback_id"].startswith("mtf_")
8+
after = client.get("/health").json()["model_version"]
9+
assert after != before # version advanced
10+
11+
12+
def test_retrain_with_explicit_version(client):
13+
r = client.post("/api/model-training/retrain", json={"model_version": "aibf-2.0.0"})
14+
assert r.status_code == 200
15+
assert client.get("/health").json()["model_version"] == "aibf-2.0.0"

0 commit comments

Comments
 (0)