""" Smoke-тест: проверяет, что все 7 быстрых проверок импортируются, создаются и запускаются на минимальной HTML-странице без ошибок. """ import pytest from pathlib import Path import sys sys.path.insert(0, str(Path(__file__).parent.parent)) from scanner.checks import ALL_CHECKS, QUICK_CHECKS from scanner.models import CheckStatus # Минимальная HTML-страница для тестирования MOCK_HTML = """ Test Site
""" class TestAllChecksImport: """Проверка что все проверки импортируются.""" def test_quick_checks_count(self): assert len(QUICK_CHECKS) == 7, f"Expected 7 quick checks, got {len(QUICK_CHECKS)}" def test_all_checks_registered(self): for name in QUICK_CHECKS: assert name in ALL_CHECKS, f"Check '{name}' not in ALL_CHECKS" @pytest.mark.parametrize("check_name", QUICK_CHECKS) def test_check_instantiable(self, check_name): """Каждая проверка создаётся без ошибок.""" check_cls = ALL_CHECKS[check_name] instance = check_cls() assert instance.name == check_name assert instance.title class TestCheckLogic: """Проверка логики проверок (без Playwright — unit tests).""" def test_form_checkbox_patterns(self): from scanner.checks.form_checkbox import FormCheckboxCheck import re check = FormCheckboxCheck() # Паттерны должны матчить русский текст согласия assert any(re.search(p, "согласие на обработку данных", re.IGNORECASE) for p in check.CONSENT_PATTERNS) assert any(re.search(p, "я согласен на обработку персональных данных", re.IGNORECASE) for p in check.CONSENT_PATTERNS) def test_operator_info_inn_pattern(self): from scanner.checks.operator_info import OperatorInfoCheck import re check = OperatorInfoCheck() m = re.search(check.INN_PATTERN, "ИНН: 0278901234", re.IGNORECASE) assert m, "INN pattern should match 10-digit INN" assert m.group(1) == "0278901234" def test_external_scripts_yaml_loads(self): from scanner.checks.external_scripts import ExternalScriptsCheck check = ExternalScriptsCheck() trackers = check._load_trackers() assert "critical" in trackers assert "russian" in trackers assert any(e["domain"] == "google-analytics.com" for e in trackers["critical"]) class TestScoring: """Проверка логики скоринга.""" def test_score_calculation_critical(self): from scanner.models import SiteScan, CheckResult, CheckStatus, RiskLevel from datetime import datetime scan = SiteScan(url="https://test.ru", scanned_at=datetime.now()) scan.checks = [ CheckResult(name="t1", title="Test 1", status=CheckStatus.FAIL, risk_level=RiskLevel.CRITICAL), CheckResult(name="t2", title="Test 2", status=CheckStatus.PASS, risk_level=RiskLevel.LOW), ] scan._calculate_score() assert scan.score == 70 # 100 - 30 assert scan.is_compliant # score=70 — на границе, но compliant def test_score_all_pass(self): from scanner.models import SiteScan, CheckResult, CheckStatus, RiskLevel from datetime import datetime scan = SiteScan(url="https://test.ru", scanned_at=datetime.now()) scan.checks = [ CheckResult(name="t1", title="Test 1", status=CheckStatus.PASS, risk_level=RiskLevel.HIGH), ] scan._calculate_score() assert scan.score == 100 assert scan.is_compliant def test_score_multiple_critical(self): from scanner.models import SiteScan, CheckResult, CheckStatus, RiskLevel from datetime import datetime scan = SiteScan(url="https://test.ru", scanned_at=datetime.now()) scan.checks = [ CheckResult(name="t1", title="C1", status=CheckStatus.FAIL, risk_level=RiskLevel.CRITICAL), CheckResult(name="t2", title="C2", status=CheckStatus.FAIL, risk_level=RiskLevel.CRITICAL), CheckResult(name="t3", title="H1", status=CheckStatus.FAIL, risk_level=RiskLevel.HIGH), ] scan._calculate_score() assert scan.score == 20 # 100 - 30 - 30 - 20 assert not scan.is_compliant class TestEnginePhases: """Проверка фазовой модели в engine.""" def test_mutating_checks_identified(self): """cookie_audit должен быть в mutating_checks.""" from scanner.checks import QUICK_CHECKS mutating = {"cookie_audit"} assert "cookie_audit" in QUICK_CHECKS, "cookie_audit должен быть в QUICK_CHECKS" non_mutating = [n for n in QUICK_CHECKS if n not in mutating] assert len(non_mutating) == 6 # 7 quick - 1 mutating