import pytest from unittest.mock import patch, MagicMock from io import BytesIO # Import all functions to test from utils import ( extract_keywords, parse_resume, extract_email, score_candidate, summarize_resume, filter_resumes_by_keywords, evaluate_resumes, store_in_supabase, generate_pdf_report, generate_interview_questions_from_summaries ) # Run Command for Full Coverage Report: pytest --cov=utils --cov-report=term-missing -v # --- Mock Models and External APIs --- @pytest.fixture(autouse=True) def patch_embedding_model(monkeypatch): mock_model = MagicMock() mock_model.encode.return_value = [0.1, 0.2, 0.3] monkeypatch.setattr("utils.embedding_model", mock_model) @pytest.fixture(autouse=True) def patch_spacy(monkeypatch): nlp_mock = MagicMock() nlp_mock.return_value = [MagicMock(text="python", pos_="NOUN", is_stop=False)] monkeypatch.setattr("utils.nlp", nlp_mock) # --- extract_keywords --- def test_extract_keywords(): text = "We are looking for a Python developer with Django and REST experience." keywords = extract_keywords(text) assert isinstance(keywords, list) assert "python" in keywords or len(keywords) > 0 # --- parse_resume --- def test_parse_resume(): dummy_pdf = MagicMock() dummy_pdf.read.return_value = b"%PDF-1.4" with patch("fitz.open") as mocked_fitz: page_mock = MagicMock() page_mock.get_text.return_value = "Resume Text Here" mocked_fitz.return_value = [page_mock] result = parse_resume(dummy_pdf) assert "Resume Text" in result # --- extract_email --- def test_extract_email(): text = "Contact me at johndoe@example.com for more info." assert extract_email(text) == "johndoe@example.com" assert extract_email("No email here!") is None # --- score_candidate --- def test_score_candidate(): score = score_candidate("Experienced Python developer", "Looking for Python engineer") assert isinstance(score, float) assert 0 <= score <= 1 # --- summarize_resume --- @patch("utils.query") def test_summarize_resume(mock_query): mock_query.return_value = [{"generated_text": "This is a summary"}] summary = summarize_resume("This is a long resume text.") assert summary == "This is a summary" mock_query.return_value = None fallback = summarize_resume("Another resume") assert "unavailable" in fallback.lower() # --- filter_resumes_by_keywords --- def test_filter_resumes_by_keywords(): resumes = [ {"name": "John", "resume": "python django rest api"}, {"name": "Doe", "resume": "java spring"} ] job_description = "Looking for a python developer with API knowledge." filtered, removed = filter_resumes_by_keywords(resumes, job_description, min_keyword_match=1) assert isinstance(filtered, list) assert isinstance(removed, list) assert len(filtered) + len(removed) == 2 # --- evaluate_resumes --- @patch("utils.parse_resume", return_value="python flask api") @patch("utils.extract_email", return_value="test@example.com") @patch("utils.summarize_resume", return_value="A senior Python developer.") @patch("utils.score_candidate", return_value=0.85) def test_evaluate_resumes(_, __, ___, ____): class DummyFile: def __init__(self, name): self.name = name def read(self): return b"%PDF-1.4" uploaded_files = [DummyFile("resume1.pdf")] job_desc = "Looking for a python developer." shortlisted, removed = evaluate_resumes(uploaded_files, job_desc) assert len(shortlisted) == 1 assert isinstance(removed, list) # --- store_in_supabase --- @patch("utils.supabase") def test_store_in_supabase(mock_supabase): table_mock = MagicMock() table_mock.insert.return_value.execute.return_value = {"status": "success"} mock_supabase.table.return_value = table_mock response = store_in_supabase("text", 0.8, "John", "john@example.com", "summary") assert "status" in response # --- generate_pdf_report --- def test_generate_pdf_report(): candidates = [{ "name": "John Doe", "email": "john@example.com", "score": 0.87, "summary": "Python developer" }] pdf = generate_pdf_report(candidates, questions=["What are your strengths?"]) assert isinstance(pdf, BytesIO) # --- generate_interview_questions_from_summaries --- @patch("utils.client.chat_completion") def test_generate_interview_questions_from_summaries(mock_chat): mock_chat.return_value.choices = [ MagicMock(message=MagicMock(content=""" 1. What are your strengths? 2. Describe a project you've led. 3. How do you handle tight deadlines? """)) ] candidates = [{"summary": "Experienced Python developer"}] questions = generate_interview_questions_from_summaries(candidates) assert len(questions) > 0 assert all(q.startswith("Q") for q in questions) @patch("utils.supabase") def test_store_in_supabase(mock_supabase): mock_table = MagicMock() mock_execute = MagicMock() mock_execute.return_value = {"status": "success"} # Attach mocks mock_table.insert.return_value.execute = mock_execute mock_supabase.table.return_value = mock_table data = { "resume_text": "Some text", "score": 0.85, "candidate_name": "Alice", "email": "alice@example.com", "summary": "Experienced backend developer" } response = store_in_supabase(**data) assert response["status"] == "success" mock_supabase.table.assert_called_once_with("candidates") mock_table.insert.assert_called_once() inserted_data = mock_table.insert.call_args[0][0] assert inserted_data["name"] == "Alice" assert inserted_data["email"] == "alice@example.com" def test_extract_keywords_empty_input(): assert extract_keywords("") == [] def test_extract_email_malformed(): malformed_text = "email at example dot com" assert extract_email(malformed_text) is None def test_score_candidate_failure(monkeypatch): def broken_encode(*args, **kwargs): raise Exception("fail") monkeypatch.setattr("utils.embedding_model.encode", broken_encode) score = score_candidate("resume", "job description") assert score == 0 @patch("utils.query") def test_summarize_resume_bad_response(mock_query): mock_query.return_value = {"weird_key": "no summary here"} summary = summarize_resume("Resume text") assert "unavailable" in summary.lower() @patch("utils.query") def test_summarize_resume_bad_response(mock_query): mock_query.return_value = {"weird_key": "no summary here"} summary = summarize_resume("Resume text") assert "unavailable" in summary.lower() @patch("utils.parse_resume", return_value="some text") @patch("utils.extract_email", return_value=None) @patch("utils.summarize_resume", return_value="Summary here") @patch("utils.score_candidate", return_value=0.1) def test_evaluate_resumes_low_score_filtered(_, __, ___, ____): class Dummy: name = "resume.pdf" def read(self): return b"%PDF" uploaded = [Dummy()] shortlisted, removed = evaluate_resumes(uploaded, "job description") assert len(shortlisted) == 0 assert len(removed) == 1