""" Test suite for WarrenAnalyzer Verifica calcoli score e fair value per prevenire regressioni """ import pytest from src.analysis.warren_analyzer import WarrenAnalyzer class TestCalculateScore: """Test calculate_score() method""" def test_score_quality_stock(self, analyzer, normal_stock_data): """Stock normale di qualità dovrebbe avere score 60-80""" score = analyzer.calculate_score(normal_stock_data) assert isinstance(score, int), "Score deve essere int" assert 60 <= score <= 80, f"Score {score} fuori range atteso per stock di qualità" def test_score_luxury_brand(self, analyzer, luxury_stock_data): """Luxury brand (Ferrari) dovrebbe avere score boost""" normal_score = analyzer.calculate_score({**luxury_stock_data, 'sector': 'Industrials'}) luxury_score = analyzer.calculate_score(luxury_stock_data) # Luxury dovrebbe avere score più alto grazie ai multipli premium assert luxury_score >= normal_score - 10, "Luxury brand dovrebbe avere score comparabile/superiore" def test_score_financial_sector(self, analyzer, financial_stock_data): """Banca con debt alto non dovrebbe essere penalizzata eccessivamente""" score = analyzer.calculate_score(financial_stock_data) # Banche con D/E 5.0 sono normali, non dovrebbero avere score < 50 assert score >= 50, f"Banca con D/E 5.0 non dovrebbe avere score {score} < 50" def test_score_low_quality(self, analyzer, low_quality_stock_data): """Stock bassa qualità dovrebbe avere score < 50""" score = analyzer.calculate_score(low_quality_stock_data) assert score < 50, f"Low quality stock dovrebbe avere score < 50, got {score}" def test_score_range_bounds(self, analyzer, normal_stock_data): """Score deve sempre essere tra 0 e 100""" # Test con dati normali score = analyzer.calculate_score(normal_stock_data) assert 0 <= score <= 100, f"Score {score} fuori range 0-100" # Test con dati estremi extreme_data = {**normal_stock_data, 'pe_ratio': 200, 'roe': -0.5, 'debt_to_equity': 10} score_extreme = analyzer.calculate_score(extreme_data) assert 0 <= score_extreme <= 100, f"Score {score_extreme} fuori range 0-100" class TestCalculateFairValue: """Test calculate_fair_value() method""" def test_fair_value_positive(self, analyzer, normal_stock_data): """Fair value deve essere sempre positivo""" fair_value = analyzer.calculate_fair_value(normal_stock_data) assert fair_value > 0, "Fair value deve essere positivo" def test_fair_value_growth_positive(self, analyzer, normal_stock_data): """Growth positivo dovrebbe portare fair value > price""" # Stock con growth 12% dovrebbe avere fair value superiore fair_value = analyzer.calculate_fair_value(normal_stock_data) # Con PE 15, PB 2, ROE 15%, growth 12% → fair value dovrebbe essere > price assert fair_value >= normal_stock_data['price'] * 0.8, \ f"Fair value {fair_value} troppo basso rispetto a price {normal_stock_data['price']}" def test_fair_value_growth_negative(self, analyzer, crisis_stock_data): """Growth negativo NON deve causare fair value negativo""" fair_value = analyzer.calculate_fair_value(crisis_stock_data) # CRITICO: fair value deve rimanere positivo anche con earnings decline -30% assert fair_value > 0, \ f"Fair value {fair_value} è negativo con growth -30%! Bug critico!" # Fair value non dovrebbe essere superiore al prezzo in caso di crisi assert fair_value <= crisis_stock_data['price'] * 1.5, \ f"Fair value {fair_value} troppo alto per stock in crisi" def test_fair_value_sanity_cap_upper(self, analyzer, normal_stock_data): """Fair value non deve essere > 3x price""" # Modifica dati per creare scenario estremo extreme_data = {**normal_stock_data, 'pe_ratio': 5, 'earnings_growth': 0.50} fair_value = analyzer.calculate_fair_value(extreme_data) assert fair_value <= extreme_data['price'] * 3, \ f"Fair value {fair_value} > 3x price {extreme_data['price']} (sanity cap failed)" def test_fair_value_sanity_cap_lower(self, analyzer, normal_stock_data): """Fair value non deve essere < 20% price""" # Dati pessimi ma fair value ha un floor bad_data = {**normal_stock_data, 'pe_ratio': 100, 'earnings_growth': -0.50} fair_value = analyzer.calculate_fair_value(bad_data) assert fair_value >= bad_data['price'] * 0.2, \ f"Fair value {fair_value} < 20% price {bad_data['price']} (sanity floor failed)" def test_fair_value_luxury_premium(self, analyzer, luxury_stock_data): """Luxury brand dovrebbe avere fair value più alto (multipli premium)""" normal_data = {**luxury_stock_data, 'sector': 'Industrials'} fair_value_normal = analyzer.calculate_fair_value(normal_data) fair_value_luxury = analyzer.calculate_fair_value(luxury_stock_data) # Luxury dovrebbe avere fair value almeno comparabile (multipli più alti) assert fair_value_luxury >= fair_value_normal * 0.9, \ "Luxury brand dovrebbe avere fair value premium" class TestEdgeCases: """Test casi limite e gestione errori""" def test_missing_pe_ratio(self, analyzer, normal_stock_data): """Gestione PE mancante""" data = {**normal_stock_data, 'pe_ratio': None} # Non dovrebbe crashare fair_value = analyzer.calculate_fair_value(data) score = analyzer.calculate_score(data) assert fair_value > 0, "Fair value deve essere positivo anche senza PE" assert 0 <= score <= 100, "Score deve essere valido anche senza PE" def test_missing_roe(self, analyzer, normal_stock_data): """Gestione ROE mancante""" data = {**normal_stock_data, 'roe': None} # Non dovrebbe crashare score = analyzer.calculate_score(data) assert 0 <= score <= 100, "Score deve essere valido anche senza ROE" def test_zero_pe_ratio(self, analyzer, normal_stock_data): """PE = 0 dovrebbe essere gestito""" data = {**normal_stock_data, 'pe_ratio': 0} # Non dovrebbe crashare o dividere per zero fair_value = analyzer.calculate_fair_value(data) assert fair_value >= 0, "Fair value con PE=0 non deve essere negativo" def test_extreme_pe_ratio(self, analyzer, normal_stock_data): """PE estremo (1000) dovrebbe essere gestito""" data = {**normal_stock_data, 'pe_ratio': 1000} score = analyzer.calculate_score(data) fair_value = analyzer.calculate_fair_value(data) # Score dovrebbe essere basso (PE alto penalizza molto) assert score < 70, f"Stock con PE 1000 dovrebbe avere score < 70, got {score}" # Fair value dovrebbe rispettare sanity caps assert fair_value < data['price'] * 5, "Fair value con PE estremo dovrebbe avere cap" class TestSectorDetection: """Test identificazione settori speciali""" def test_luxury_brand_detection(self, analyzer): """Test identificazione luxury brand""" assert analyzer._is_luxury_brand('RACE.MI', 'Consumer Cyclical') == True assert analyzer._is_luxury_brand('MONC.MI', 'Consumer Cyclical') == True assert analyzer._is_luxury_brand('ENEL.MI', 'Utilities') == False def test_financial_sector_detection(self, analyzer): """Test identificazione settore finanziario""" assert analyzer._is_financial_sector('ISP.MI', 'Financial Services') == True assert analyzer._is_financial_sector('UCG.MI', 'Financial Services') == True assert analyzer._is_financial_sector('ENEL.MI', 'Utilities') == False