""" Unit tests for the gradio module. """ import unittest from pathlib import Path from unittest.mock import patch from functions import gradio class TestProcessInputs(unittest.TestCase): """Test cases for the process_inputs function.""" def test_process_inputs_with_real_pdf(self): """Test process_inputs with the actual test PDF file.""" # Get path to the test PDF file test_pdf_path = Path(__file__).parent / "test_data" / "linkedin_profile.pdf" # Skip test if PDF doesn't exist (optional test data) if not test_pdf_path.exists(): self.skipTest(f"Test PDF file not found: {test_pdf_path}") with patch('functions.gradio.extract_text') as mock_extract, \ patch('functions.gradio.get_github_repositories') as mock_github, \ patch('functions.gradio.summarize_job_call') as mock_job_call, \ patch('functions.gradio.write_resume') as mock_write_resume: mock_extract.return_value = {"test": "data"} mock_github.return_value = [{"name": "test-repo"}] mock_job_call.return_value = {"title": "Software Engineer", "requirements": ["Python"]} mock_write_resume.return_value = "# Generated Resume\n\nTest resume content" result = gradio.process_inputs( linkedin_pdf_path=str(test_pdf_path), github_username="testuser", job_post_text="Software engineer position" ) # Verify all functions were called mock_extract.assert_called_once_with(str(test_pdf_path)) mock_github.assert_called_once_with("testuser") mock_job_call.assert_called_once_with("Software engineer position") mock_write_resume.assert_called_once_with( {"test": "data"}, [{"name": "test-repo"}], {"title": "Software Engineer", "requirements": ["Python"]} ) # Function should return the generated resume self.assertEqual(result, "# Generated Resume\n\nTest resume content") @patch('functions.gradio.write_resume') @patch('functions.gradio.summarize_job_call') @patch('functions.gradio.extract_text') @patch('functions.gradio.get_github_repositories') def test_process_inputs_with_pdf_path_mocked( self, mock_github, mock_extract, mock_job_call, mock_write_resume ): """Test process_inputs with a PDF file path (mocked for controlled testing).""" # Mock successful LinkedIn text extraction mock_extract.return_value = { "contact_info": "John Doe, email@example.com", "summary": "Experienced software engineer", "experience": "Software Engineer at Company" } mock_github.return_value = [{"name": "test-repo"}] mock_job_call.return_value = { "title": "Software Engineer", "requirements": ["Python", "JavaScript"] } mock_write_resume.return_value = "# John Doe\n\n## Summary\nExperienced software engineer" result = gradio.process_inputs( linkedin_pdf_path="/path/to/resume.pdf", github_username="testuser", job_post_text="Software engineer position" ) # Verify extract_text was called with the correct path mock_extract.assert_called_once_with("/path/to/resume.pdf") # Verify get_github_repositories was called with username mock_github.assert_called_once_with("testuser") # Verify job post was processed mock_job_call.assert_called_once_with("Software engineer position") # Verify resume generation was called with correct arguments mock_write_resume.assert_called_once() # Function should return the generated resume content self.assertEqual(result, "# John Doe\n\n## Summary\nExperienced software engineer") @patch('functions.gradio.write_resume') @patch('functions.gradio.summarize_job_call') @patch('functions.gradio.extract_text') @patch('functions.gradio.get_github_repositories') def test_process_inputs_extraction_failure( self, mock_github, mock_extract, mock_job_call, mock_write_resume ): """Test process_inputs when LinkedIn extraction fails.""" # Mock failed LinkedIn text extraction mock_extract.return_value = None mock_github.return_value = None mock_job_call.return_value = None result = gradio.process_inputs( linkedin_pdf_path="/path/to/resume.pdf", github_username="testuser", job_post_text="Software engineer position" ) # Verify extract_text was called mock_extract.assert_called_once_with("/path/to/resume.pdf") mock_github.assert_called_once_with("testuser") mock_job_call.assert_called_once_with("Software engineer position") # write_resume should NOT be called when data is missing mock_write_resume.assert_not_called() # Function should return empty string when processing fails self.assertEqual(result, "") @patch('functions.gradio.write_resume') @patch('functions.gradio.summarize_job_call') @patch('functions.gradio.extract_text') @patch('functions.gradio.get_github_repositories') def test_process_inputs_no_pdf_path( self, mock_github, mock_extract, mock_job_call, mock_write_resume ): """Test process_inputs with no PDF path provided.""" mock_extract.return_value = None mock_github.return_value = [] mock_job_call.return_value = {"title": "Software Engineer"} result = gradio.process_inputs( linkedin_pdf_path=None, github_username="testuser", job_post_text="Software engineer position" ) # extract_text should be called with None mock_extract.assert_called_once_with(None) mock_github.assert_called_once_with("testuser") mock_job_call.assert_called_once_with("Software engineer position") # write_resume should NOT be called when LinkedIn data is missing mock_write_resume.assert_not_called() # Function should return empty string when data is insufficient self.assertEqual(result, "") @patch('functions.gradio.write_resume') @patch('functions.gradio.summarize_job_call') @patch('functions.gradio.extract_text') @patch('functions.gradio.get_github_repositories') def test_process_inputs_with_long_job_post( self, mock_github, mock_extract, mock_job_call, mock_write_resume ): """Test process_inputs with a long job post text (for logging truncation).""" mock_extract.return_value = { "summary": "Test summary" } mock_github.return_value = [] mock_job_call.return_value = {"title": "Software Engineer", "requirements": ["Python"]} long_job_post = "This is a very long job posting " * 50 # Make it longer than 100 chars result = gradio.process_inputs( linkedin_pdf_path="/path/to/resume.pdf", github_username="testuser", job_post_text=long_job_post ) # Verify extract_text was called mock_extract.assert_called_once_with("/path/to/resume.pdf") mock_github.assert_called_once_with("testuser") mock_job_call.assert_called_once_with(long_job_post.strip()) # write_resume should NOT be called when GitHub repos are empty mock_write_resume.assert_not_called() # Function should return empty string when GitHub data is missing self.assertEqual(result, "") @patch('functions.gradio.write_resume') @patch('functions.gradio.summarize_job_call') @patch('functions.gradio.extract_text') @patch('functions.gradio.get_github_repositories') def test_process_inputs_github_username_whitespace( self, mock_github, mock_extract, mock_job_call, mock_write_resume ): """Test that github_username is properly stripped of whitespace.""" mock_extract.return_value = None mock_github.return_value = [] mock_job_call.return_value = {"title": "Engineer"} result = gradio.process_inputs( linkedin_pdf_path=None, github_username=" testuser ", job_post_text="" ) # Verify get_github_repositories was called with stripped username mock_github.assert_called_once_with("testuser") mock_write_resume.assert_not_called() self.assertEqual(result, "") @patch('functions.gradio.write_resume') @patch('functions.gradio.summarize_job_call') @patch('functions.gradio.extract_text') @patch('functions.gradio.get_github_repositories') @patch('logging.getLogger') def test_logging_calls( self, mock_get_logger, mock_github, mock_extract, mock_job_call, mock_write_resume ): """Test that appropriate logging calls are made.""" mock_logger = mock_get_logger.return_value mock_extract.return_value = {"test": "data"} mock_github.return_value = [{"name": "repo"}] mock_job_call.return_value = {"title": "Engineer"} mock_write_resume.return_value = "# Resume Content" result = gradio.process_inputs( linkedin_pdf_path="/path/to/resume.pdf", github_username="testuser", job_post_text="Job description here" ) # Verify logging calls were made mock_logger.info.assert_called() # Verify resume was generated successfully self.assertEqual(result, "# Resume Content") @patch('functions.gradio.write_resume') @patch('functions.gradio.summarize_job_call') @patch('functions.gradio.extract_text') @patch('functions.gradio.get_github_repositories') def test_process_inputs_write_resume_exception( self, mock_github, mock_extract, mock_job_call, mock_write_resume ): """Test process_inputs when write_resume raises an exception.""" mock_extract.return_value = {"test": "data"} mock_github.return_value = [{"name": "repo"}] mock_job_call.return_value = {"title": "Engineer"} mock_write_resume.side_effect = Exception("API Error") result = gradio.process_inputs( linkedin_pdf_path="/path/to/resume.pdf", github_username="testuser", job_post_text="Job description here" ) # Verify all functions were called mock_extract.assert_called_once_with("/path/to/resume.pdf") mock_github.assert_called_once_with("testuser") mock_job_call.assert_called_once_with("Job description here") mock_write_resume.assert_called_once() # Function should return empty string when write_resume fails self.assertEqual(result, "") @patch('functions.gradio.write_resume') @patch('functions.gradio.summarize_job_call') @patch('functions.gradio.extract_text') @patch('functions.gradio.get_github_repositories') def test_process_inputs_complete_success_flow( self, mock_github, mock_extract, mock_job_call, mock_write_resume ): """Test the complete successful flow with all components working.""" # Mock all successful responses linkedin_data = { "contact_info": "Jane Doe, jane@example.com", "summary": "Senior Python Developer", "experience": "5 years experience in Python development" } github_repos = [ {"name": "awesome-python-app", "description": "A Python web application"}, {"name": "data-analysis-tool", "description": "Data analysis with pandas"} ] job_data = { "title": "Senior Python Developer", "requirements": ["Python", "Django", "PostgreSQL"], "company": "Tech Corp" } resume_content = ( "# Jane Doe\n\n## Experience\n" "Senior Python Developer with 5 years experience..." ) mock_extract.return_value = linkedin_data mock_github.return_value = github_repos mock_job_call.return_value = job_data mock_write_resume.return_value = resume_content result = gradio.process_inputs( linkedin_pdf_path="/path/to/jane_resume.pdf", github_username="jane_dev", job_post_text="We are looking for a Senior Python Developer with Django experience..." ) # Verify all functions were called with correct arguments mock_extract.assert_called_once_with("/path/to/jane_resume.pdf") mock_github.assert_called_once_with("jane_dev") mock_job_call.assert_called_once_with( "We are looking for a Senior Python Developer with Django experience..." ) mock_write_resume.assert_called_once_with(linkedin_data, github_repos, job_data) # Verify the complete resume is returned self.assertEqual(result, resume_content) self.assertIn("Jane Doe", result) self.assertIn("Senior Python Developer", result) @patch('functions.gradio.write_resume') @patch('functions.gradio.summarize_job_call') @patch('functions.gradio.extract_text') @patch('functions.gradio.get_github_repositories') def test_process_inputs_none_github_username( self, mock_github, mock_extract, mock_job_call, mock_write_resume ): """Test process_inputs with None github_username (should handle gracefully).""" mock_extract.return_value = None mock_github.return_value = None mock_job_call.return_value = None # This should raise a TypeError due to the bug in gradio.py # where it tries to slice job_post_text[:100] when job_post_text is None with self.assertRaises(TypeError): gradio.process_inputs( linkedin_pdf_path=None, github_username=None, job_post_text=None ) if __name__ == '__main__': unittest.main()