""" Unit tests for Programming Assistance Service Tests the specialized programming assistance features including code explanation, debugging, error analysis, code review, and beginner-friendly explanations. """ import unittest from unittest.mock import Mock, patch, MagicMock import pytest from chat_agent.services.programming_assistance import ( ProgrammingAssistanceService, AssistanceType, CodeAnalysis, ErrorAnalysis ) class TestProgrammingAssistanceService(unittest.TestCase): """Test cases for ProgrammingAssistanceService.""" def setUp(self): """Set up test fixtures.""" self.service = ProgrammingAssistanceService() def test_initialization(self): """Test service initialization.""" self.assertIsInstance(self.service, ProgrammingAssistanceService) self.assertIsNotNone(self.service.code_patterns) self.assertIsNotNone(self.service.error_patterns) def test_get_assistance_prompt_template_code_explanation(self): """Test getting prompt template for code explanation.""" template = self.service.get_assistance_prompt_template( AssistanceType.CODE_EXPLANATION, 'python' ) self.assertIn('expert python programming tutor', template) self.assertIn('Explain the provided python code', template) self.assertIn('step-by-step breakdown', template) def test_get_assistance_prompt_template_debugging(self): """Test getting prompt template for debugging.""" template = self.service.get_assistance_prompt_template( AssistanceType.DEBUGGING, 'javascript' ) self.assertIn('Help debug the provided javascript code', template) self.assertIn('Step-by-step solution', template) self.assertIn('corrected code', template) def test_get_assistance_prompt_template_with_beginner_context(self): """Test prompt template with beginner context.""" template = self.service.get_assistance_prompt_template( AssistanceType.CONCEPT_CLARIFICATION, 'python', {'beginner_mode': True} ) self.assertIn('beginner', template.lower()) self.assertIn('simple language', template) self.assertIn('step-by-step', template) def test_analyze_code_python_function(self): """Test code analysis for Python function.""" code = """ def greet(name): print("Hello, " + name + "!") return "Greeting sent" greet("Alice") """ analysis = self.service.analyze_code(code, 'python') self.assertIsInstance(analysis, CodeAnalysis) self.assertEqual(analysis.language, 'python') self.assertEqual(analysis.code_type, 'function_def') self.assertIn(analysis.complexity_level, ['beginner', 'intermediate', 'advanced']) self.assertIsInstance(analysis.issues_found, list) self.assertIsInstance(analysis.suggestions, list) self.assertIsInstance(analysis.explanation, str) def test_analyze_code_javascript_class(self): """Test code analysis for JavaScript class.""" code = """ class Person { constructor(name) { this.name = name; } greet() { console.log("Hello, " + this.name); } } const person = new Person("Bob"); person.greet(); """ analysis = self.service.analyze_code(code, 'javascript') self.assertIsInstance(analysis, CodeAnalysis) self.assertEqual(analysis.language, 'javascript') self.assertEqual(analysis.code_type, 'class_def') def test_analyze_code_with_issues(self): """Test code analysis that finds issues.""" code = """ def check_empty(items): if len(items) == 0: return True return False result = check_empty([]) print result # Python 2 syntax """ analysis = self.service.analyze_code(code, 'python') self.assertGreater(len(analysis.issues_found), 0) self.assertGreater(len(analysis.suggestions), 0) # Check for specific issues issues_text = ' '.join(analysis.issues_found) self.assertIn('print', issues_text.lower()) def test_analyze_error_python_name_error(self): """Test error analysis for Python NameError.""" error_message = "NameError: name 'variable_name' is not defined" analysis = self.service.analyze_error(error_message, language='python') self.assertIsInstance(analysis, ErrorAnalysis) self.assertEqual(analysis.error_type, 'name_error') self.assertIn('not defined', analysis.error_message) self.assertGreater(len(analysis.likely_causes), 0) self.assertGreater(len(analysis.solutions), 0) # Check for specific causes and solutions causes_text = ' '.join(analysis.likely_causes) self.assertIn('misspelled', causes_text.lower()) solutions_text = ' '.join(analysis.solutions) self.assertIn('spelling', solutions_text.lower()) def test_analyze_error_javascript_reference_error(self): """Test error analysis for JavaScript ReferenceError.""" error_message = "ReferenceError: myVariable is not defined" analysis = self.service.analyze_error(error_message, language='javascript') self.assertIsInstance(analysis, ErrorAnalysis) self.assertEqual(analysis.error_type, 'reference_error') def test_analyze_error_with_code(self): """Test error analysis with code context.""" error_message = "TypeError: unsupported operand type(s) for +: 'int' and 'str'" code = """ age = 25 name = "Alice" result = age + name # This causes the error """ analysis = self.service.analyze_error(error_message, code, 'python') self.assertEqual(analysis.error_type, 'type_error') self.assertGreater(len(analysis.code_fixes), 0) def test_generate_beginner_explanation_variables(self): """Test beginner explanation for variables.""" explanation = self.service.generate_beginner_explanation('variables', 'python') self.assertIn('What is variables?', explanation) self.assertIn('Why do we use variables?', explanation) self.assertIn('Simple Example:', explanation) self.assertIn('```python', explanation) self.assertIn('What to Learn Next:', explanation) def test_generate_beginner_explanation_functions(self): """Test beginner explanation for functions.""" explanation = self.service.generate_beginner_explanation('functions', 'javascript') self.assertIn('What is functions?', explanation) self.assertIn('reusable blocks', explanation.lower()) self.assertIn('```javascript', explanation) def test_generate_beginner_explanation_with_code_example(self): """Test beginner explanation with provided code example.""" code_example = "x = 10\ny = 20\nsum = x + y" explanation = self.service.generate_beginner_explanation( 'variables', 'python', code_example ) self.assertIn(code_example, explanation) self.assertIn('```python', explanation) def test_detect_assistance_type_error_keywords(self): """Test assistance type detection for error-related messages.""" test_cases = [ ("I'm getting an error", AssistanceType.ERROR_ANALYSIS), ("My code is broken", AssistanceType.ERROR_ANALYSIS), ("This is not working properly", AssistanceType.ERROR_ANALYSIS), ("Exception occurred", AssistanceType.ERROR_ANALYSIS) ] for message, expected_type in test_cases: with self.subTest(message=message): detected_type = self.service.detect_assistance_type(message) self.assertEqual(detected_type, expected_type) def test_detect_assistance_type_with_code(self): """Test assistance type detection when code is provided.""" code = "def hello(): print('Hello')" test_cases = [ ("I have an error", AssistanceType.DEBUGGING), ("Explain this code", AssistanceType.CODE_EXPLANATION), ("Review my code", AssistanceType.CODE_REVIEW) ] for message, expected_type in test_cases: with self.subTest(message=message): detected_type = self.service.detect_assistance_type(message, code) self.assertEqual(detected_type, expected_type) def test_detect_assistance_type_beginner_keywords(self): """Test assistance type detection for beginner-related messages.""" test_cases = [ ("I'm a beginner", AssistanceType.BEGINNER_HELP), ("I'm new to programming", AssistanceType.BEGINNER_HELP), ("Just started learning", AssistanceType.BEGINNER_HELP), ("Basic question about", AssistanceType.BEGINNER_HELP) ] for message, expected_type in test_cases: with self.subTest(message=message): detected_type = self.service.detect_assistance_type(message) self.assertEqual(detected_type, expected_type) def test_detect_assistance_type_explanation_keywords(self): """Test assistance type detection for explanation requests.""" test_cases = [ ("What does this mean?", AssistanceType.CONCEPT_CLARIFICATION), ("How does this work?", AssistanceType.CONCEPT_CLARIFICATION), ("Explain loops", AssistanceType.CONCEPT_CLARIFICATION), ("I don't understand", AssistanceType.CONCEPT_CLARIFICATION) ] for message, expected_type in test_cases: with self.subTest(message=message): detected_type = self.service.detect_assistance_type(message) self.assertEqual(detected_type, expected_type) def test_format_assistance_response_code_explanation(self): """Test formatting code explanation response.""" analysis = CodeAnalysis( code_type='function_def', language='python', complexity_level='beginner', issues_found=['Missing docstring'], suggestions=['Add function documentation'], explanation='This is a simple function that greets users.' ) response = self.service.format_assistance_response( AssistanceType.CODE_EXPLANATION, analysis, 'python' ) self.assertIn('Code Analysis', response) self.assertIn('Python', response) self.assertIn('Function Def', response) self.assertIn('Beginner', response) self.assertIn('Missing docstring', response) self.assertIn('Add function documentation', response) def test_format_assistance_response_error_analysis(self): """Test formatting error analysis response.""" analysis = ErrorAnalysis( error_type='name_error', error_message='name is not defined', likely_causes=['Variable misspelled'], solutions=['Check spelling'], code_fixes=['Fix variable name'] ) response = self.service.format_assistance_response( AssistanceType.ERROR_ANALYSIS, analysis, 'python' ) self.assertIn('Error Analysis', response) self.assertIn('Name Error', response) self.assertIn('Variable misspelled', response) self.assertIn('Check spelling', response) self.assertIn('Fix variable name', response) def test_format_assistance_response_code_review(self): """Test formatting code review response.""" analysis = CodeAnalysis( code_type='script', language='javascript', complexity_level='intermediate', issues_found=['Using var instead of let'], suggestions=['Use modern JavaScript syntax'], explanation='This script demonstrates basic concepts.' ) response = self.service.format_assistance_response( AssistanceType.CODE_REVIEW, analysis, 'javascript' ) self.assertIn('Code Review', response) self.assertIn('intermediate', response) self.assertIn('Using var instead of let', response) self.assertIn('Use modern JavaScript syntax', response) self.assertIn('Keep up the great work', response) def test_code_pattern_detection_python(self): """Test code pattern detection for Python.""" test_cases = [ ("def my_function():", 'function_def'), ("class MyClass:", 'class_def'), ("import os", 'import'), ("for i in range(10):", 'loop'), ("if condition:", 'conditional'), ("try:", 'exception') ] for code, expected_type in test_cases: with self.subTest(code=code): detected_type = self.service._detect_code_type(code, 'python') self.assertEqual(detected_type, expected_type) def test_code_pattern_detection_javascript(self): """Test code pattern detection for JavaScript.""" test_cases = [ ("function myFunc() {}", 'function_def'), ("const func = () => {}", 'function_def'), ("class MyClass {}", 'class_def'), ("import React from 'react'", 'import'), ("for (let i = 0; i < 10; i++) {}", 'loop'), ("if (condition) {}", 'conditional') ] for code, expected_type in test_cases: with self.subTest(code=code): detected_type = self.service._detect_code_type(code, 'javascript') self.assertEqual(detected_type, expected_type) def test_complexity_assessment(self): """Test code complexity assessment.""" # Beginner level (short code) simple_code = "x = 5\nprint(x)" complexity = self.service._assess_complexity(simple_code, 'python') self.assertEqual(complexity, 'beginner') # Intermediate level (medium code) medium_code = "\n".join([f"line_{i} = {i}" for i in range(10)]) complexity = self.service._assess_complexity(medium_code, 'python') self.assertEqual(complexity, 'intermediate') # Advanced level (long code) long_code = "\n".join([f"line_{i} = {i}" for i in range(25)]) complexity = self.service._assess_complexity(long_code, 'python') self.assertEqual(complexity, 'advanced') def test_error_type_detection_python(self): """Test error type detection for Python errors.""" test_cases = [ ("NameError: name 'x' is not defined", 'name_error'), ("SyntaxError: invalid syntax", 'syntax_error'), ("TypeError: unsupported operand", 'type_error'), ("IndexError: list index out of range", 'index_error'), ("KeyError: 'missing_key'", 'key_error'), ("AttributeError: has no attribute", 'attribute_error'), ("ImportError: No module named", 'import_error') ] for error_msg, expected_type in test_cases: with self.subTest(error_msg=error_msg): detected_type = self.service._detect_error_type(error_msg, 'python') self.assertEqual(detected_type, expected_type) def test_error_type_detection_javascript(self): """Test error type detection for JavaScript errors.""" test_cases = [ ("ReferenceError: x is not defined", 'reference_error'), ("SyntaxError: Unexpected token", 'syntax_error'), ("TypeError: x is not a function", 'type_error'), ("RangeError: Invalid array length", 'range_error') ] for error_msg, expected_type in test_cases: with self.subTest(error_msg=error_msg): detected_type = self.service._detect_error_type(error_msg, 'javascript') self.assertEqual(detected_type, expected_type) def test_clean_error_message(self): """Test error message cleaning.""" messy_error = """ Traceback (most recent call last): File "test.py", line 5, in print(undefined_var) NameError: name 'undefined_var' is not defined """ clean_error = self.service._clean_error_message(messy_error) self.assertEqual(clean_error, "NameError: name 'undefined_var' is not defined") def test_find_code_issues_python(self): """Test finding code issues in Python.""" problematic_code = """ print "Hello World" # Python 2 syntax if x == True: # Explicit True comparison if len(items) == 0: # Length check pass """ issues = self.service._find_code_issues(problematic_code, 'python') self.assertGreater(len(issues), 0) issues_text = ' '.join(issues) self.assertIn('print', issues_text.lower()) def test_find_code_issues_javascript(self): """Test finding code issues in JavaScript.""" problematic_code = """ var x = 5; // Using var if (x == "5") { // Loose equality console.log("Equal"); } """ issues = self.service._find_code_issues(problematic_code, 'javascript') self.assertGreater(len(issues), 0) issues_text = ' '.join(issues) self.assertIn('var', issues_text.lower()) self.assertIn('equality', issues_text.lower()) def test_generate_code_suggestions(self): """Test generating code improvement suggestions.""" issues = [ "Using Python 2 print syntax - should use print() function", "Using loose equality (==) - consider strict equality (===)" ] suggestions = self.service._generate_code_suggestions("test code", 'python', issues) self.assertGreater(len(suggestions), 0) suggestions_text = ' '.join(suggestions) self.assertIn('Python 3', suggestions_text) def test_concept_info_retrieval(self): """Test retrieving concept information.""" # Test known concept variables_info = self.service._get_concept_info('variables', 'python') self.assertIn('simple_definition', variables_info) self.assertIn('purpose', variables_info) self.assertIn('example', variables_info) # Test unknown concept unknown_info = self.service._get_concept_info('unknown_concept', 'python') self.assertIn('simple_definition', unknown_info) self.assertIn('important programming concept', unknown_info['simple_definition']) def test_error_handling_in_analyze_code(self): """Test error handling in code analysis.""" # Test with invalid input analysis = self.service.analyze_code("", "invalid_language") self.assertIsInstance(analysis, CodeAnalysis) self.assertEqual(analysis.language, "invalid_language") self.assertEqual(analysis.code_type, "script") # Default fallback def test_error_handling_in_analyze_error(self): """Test error handling in error analysis.""" # Test with empty error message analysis = self.service.analyze_error("", language="python") self.assertIsInstance(analysis, ErrorAnalysis) self.assertEqual(analysis.error_type, "unknown_error") def test_error_handling_in_beginner_explanation(self): """Test error handling in beginner explanation generation.""" # This should not raise an exception explanation = self.service.generate_beginner_explanation("", "python") self.assertIsInstance(explanation, str) self.assertGreater(len(explanation), 0) if __name__ == '__main__': unittest.main()