ai-talent-finder-backend / test_phase1_integration.py
ilyass yani
Deploiement backend dans HF Spaces
9df97a2
Raw
History Blame
12.6 kB
"""
Comprehensive integration tests for Phase 1 IA enhancements.
Tests integration of:
1. Adaptive Thresholds
2. Smart Deduplication
3. Explainability Engine
4. Smart Fallback Responder
5. Skill Quality Analyzer
"""
import json
import sys
from pathlib import Path
# Add backend to path
sys.path.insert(0, str(Path(__file__).parent))
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from app.models.models import Base, Candidate, Skill, CandidateSkill, JobCriteria, CriteriaSkill, MatchResult, ProficiencyLevel
from app.services.matching_engine import (
score_candidate_against_criteria,
build_skill_universe,
get_adaptive_thresholds,
generate_enriched_explanation,
extract_candidate_skill_names,
)
# ============================================================================
# TEST FIXTURES
# ============================================================================
def setup_test_db() -> Session:
"""Create in-memory SQLite database for testing."""
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
SessionLocal = sessionmaker(bind=engine)
return SessionLocal()
def create_test_skills(db: Session) -> dict:
"""Create test skill records."""
skills_data = {
"Python": "python",
"React": "react",
"TypeScript": "typescript",
"Docker": "docker",
"AWS": "aws",
"SQL": "sql",
}
skills = {}
for name, _ in skills_data.items():
skill = Skill(name=name, category="tech")
db.add(skill)
db.flush()
skills[name] = skill
db.commit()
return skills
def create_test_candidate(db: Session, name: str, skills: list) -> Candidate:
"""Create test candidate with skills."""
candidate = Candidate(
full_name=name,
email=f"{name.lower()}@test.com",
raw_text=f"{name} CV with skills: {', '.join(skills)}",
is_fully_extracted=True,
extraction_quality_score=95,
)
db.add(candidate)
db.flush()
# Add skills
for skill_name in skills:
skill = db.query(Skill).filter(Skill.name.ilike(skill_name)).first()
if not skill:
skill = Skill(name=skill_name, category="tech")
db.add(skill)
db.flush()
cs = CandidateSkill(
candidate_id=candidate.id,
skill_id=skill.id,
proficiency_level=ProficiencyLevel.intermediate,
source="test",
)
db.add(cs)
db.commit()
return candidate
def create_test_criteria(db: Session, title: str, required_skills: dict) -> JobCriteria:
"""Create test job criteria."""
criteria = JobCriteria(
recruiter_id=1,
title=title,
description=f"Test criteria for {title}",
)
db.add(criteria)
db.flush()
# Add required skills
for skill_name, weight in required_skills.items():
skill = db.query(Skill).filter(Skill.name.ilike(skill_name)).first()
if not skill:
skill = Skill(name=skill_name, category="tech")
db.add(skill)
db.flush()
cs = CriteriaSkill(
criteria_id=criteria.id,
skill_id=skill.id,
weight=weight
)
db.add(cs)
db.commit()
return criteria
# ============================================================================
# TEST SUITES
# ============================================================================
def test_adaptive_thresholds():
"""Test adaptive threshold engine integration."""
print("\n📊 Testing Adaptive Thresholds...")
# Test default case
thresholds = get_adaptive_thresholds()
assert "accept" in thresholds, "Missing accept threshold"
assert "review" in thresholds, "Missing review threshold"
assert thresholds["accept"] > thresholds["review"], "Accept threshold should be higher"
print(" ✅ Default thresholds: PASS")
# Test domain-specific thresholds
data_scientist_thresholds = get_adaptive_thresholds("Senior Data Scientist")
assert data_scientist_thresholds is not None, "Should return thresholds for data scientist"
print(f" ✅ Data Scientist thresholds: accept={data_scientist_thresholds.get('accept')}, review={data_scientist_thresholds.get('review')}")
# Test another domain
frontend_thresholds = get_adaptive_thresholds("Senior React Developer")
assert frontend_thresholds is not None, "Should return thresholds for frontend"
print(f" ✅ Frontend thresholds: accept={frontend_thresholds.get('accept')}, review={frontend_thresholds.get('review')}")
print(" ✅ Adaptive Thresholds: ALL TESTS PASSED")
def test_smart_dedup():
"""Test smart deduplication integration."""
print("\n🔄 Testing Smart Deduplication...")
db = setup_test_db()
create_test_skills(db)
# Create candidate with duplicate skills
candidate = create_test_candidate(
db, "John Doe",
["Python", "React", "python", "PYTHON", "react", "TypeScript"]
)
# Extract skills - should be deduplicated
extracted_skills = extract_candidate_skill_names(candidate)
print(f" Extracted skills: {extracted_skills}")
# Check deduplication worked
python_count = sum(1 for s in extracted_skills if s.lower() == "python")
react_count = sum(1 for s in extracted_skills if s.lower() == "react")
assert python_count == 1, f"Python should appear once, found {python_count}"
assert react_count == 1, f"React should appear once, found {react_count}"
assert "TypeScript" in extracted_skills, "TypeScript should be present"
print(" ✅ Deduplication removed duplicates correctly")
print(" ✅ Smart Deduplication: ALL TESTS PASSED")
def test_explainability_engine():
"""Test explainability engine integration."""
print("\n📖 Testing Explainability Engine...")
db = setup_test_db()
create_test_skills(db)
# Create test data
candidate = create_test_candidate(db, "Alice", ["Python", "React", "Docker"])
criteria = create_test_criteria(
db, "Senior React Developer",
{"Python": 40, "React": 50, "TypeScript": 10}
)
# Get criteria skills for enriched explanation
criteria_skills_models = db.query(CriteriaSkill).filter(
CriteriaSkill.criteria_id == criteria.id
).all()
criteria_skills = [
{"name": cs.skill.name, "weight": cs.weight}
for cs in criteria_skills_models
]
# Score candidate
score, details = score_candidate_against_criteria(candidate, criteria_skills)
print(f" Candidate score: {score}%")
# Generate enriched explanation
enriched = generate_enriched_explanation(candidate, score, details, criteria_skills)
assert "score" in enriched, "Should have score in explanation"
assert "matched_skills" in enriched, "Should have matched_skills"
assert "missing_skills" in enriched, "Should have missing_skills"
print(f" ✅ Explanation generated: {enriched.get('summary', 'N/A')}")
print(" ✅ Explainability Engine: ALL TESTS PASSED")
def test_smart_fallback():
"""Test smart fallback responder integration."""
print("\n💬 Testing Smart Fallback...")
try:
from ai_module.chatbot.smart_fallback import SmartFallbackResponder
db = setup_test_db()
create_test_skills(db)
candidate = create_test_candidate(db, "Bob", ["Python", "Docker"])
criteria = create_test_criteria(
db, "Python Developer",
{"Python": 60, "Docker": 40}
)
responder = SmartFallbackResponder()
# Test method availability
assert hasattr(responder, 'explain_score_fallback'), "Missing explain_score_fallback"
assert hasattr(responder, 'compare_candidates_fallback'), "Missing compare_candidates_fallback"
assert hasattr(responder, 'greeting_fallback'), "Missing greeting_fallback"
print(" ✅ All SmartFallbackResponder methods present")
print(" ✅ Smart Fallback: ALL TESTS PASSED")
except ImportError:
print(" ⚠️ Smart Fallback not available (dependencies missing)")
def test_skill_quality():
"""Test skill quality analyzer integration."""
print("\n⭐ Testing Skill Quality Analyzer...")
try:
from ai_module.matching.skill_quality import SkillQualityAnalyzer
db = setup_test_db()
create_test_skills(db)
# Create multiple candidates with various skills
create_test_candidate(db, "Candidate1", ["Python", "React", "Docker"])
create_test_candidate(db, "Candidate2", ["Python", "TypeScript"])
create_test_candidate(db, "Candidate3", ["React", "AWS", "SQL"])
analyzer = SkillQualityAnalyzer()
metrics = analyzer.compute_metrics(db)
assert "quality_score" in metrics, "Missing quality_score"
assert "total_skills" in metrics, "Missing total_skills"
assert "unique_skills" in metrics, "Missing unique_skills"
assert "health_status" in metrics, "Missing health_status"
print(f" Quality Score: {metrics.get('quality_score')}")
print(f" Total Skills: {metrics.get('total_skills')}")
print(f" Unique Skills: {metrics.get('unique_skills')}")
print(f" Health Status: {metrics.get('health_status')}")
print(" ✅ Skill Quality Analyzer: ALL TESTS PASSED")
except ImportError:
print(" ⚠️ Skill Quality Analyzer not available (dependencies missing)")
def test_full_matching_pipeline():
"""Test complete matching pipeline with all Phase 1 features."""
print("\n🔄 Testing Full Matching Pipeline (End-to-End)...")
db = setup_test_db()
create_test_skills(db)
# Create candidates
candidates = [
create_test_candidate(db, "Alice", ["Python", "React", "Docker", "AWS"]),
create_test_candidate(db, "Bob", ["Python", "Django", "PostgreSQL"]),
create_test_candidate(db, "Charlie", ["JavaScript", "React", "Node.js"]),
]
# Create criteria
criteria = create_test_criteria(
db, "Senior Full Stack Developer",
{"Python": 30, "React": 40, "Docker": 20, "TypeScript": 10}
)
# Score all candidates
criteria_skills_models = db.query(CriteriaSkill).filter(
CriteriaSkill.criteria_id == criteria.id
).all()
criteria_skills = [
{"name": cs.skill.name, "weight": cs.weight}
for cs in criteria_skills_models
]
results = []
for candidate in candidates:
score, details = score_candidate_against_criteria(candidate, criteria_skills)
results.append({
"candidate": candidate.full_name,
"score": score,
"coverage": details.get("coverage"),
"matched": details.get("matched_skills"),
"missing": details.get("missing_skills"),
})
# Sort by score
results.sort(key=lambda x: x["score"], reverse=True)
print(" 📋 Final Rankings:")
for i, result in enumerate(results, 1):
print(f" {i}. {result['candidate']}: {result['score']}% "
f"(Coverage: {result['coverage']}%, Matched: {len(result['matched'])})")
# Verify ranking makes sense
assert results[0]["score"] >= results[1]["score"], "Ranking should be by score"
print(" ✅ Full Matching Pipeline: ALL TESTS PASSED")
# ============================================================================
# MAIN EXECUTION
# ============================================================================
def main():
"""Run all tests."""
print("=" * 80)
print("PHASE 1 INTEGRATION TEST SUITE")
print("=" * 80)
try:
test_adaptive_thresholds()
test_smart_dedup()
test_explainability_engine()
test_smart_fallback()
test_skill_quality()
test_full_matching_pipeline()
print("\n" + "=" * 80)
print("✅ ALL TESTS PASSED - PHASE 1 INTEGRATION SUCCESSFUL")
print("=" * 80)
return 0
except AssertionError as e:
print(f"\n❌ TEST FAILED: {e}")
return 1
except Exception as e:
print(f"\n❌ ERROR: {e}")
import traceback
traceback.print_exc()
return 1
if __name__ == "__main__":
sys.exit(main())