"""
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
Мы используем cookies
"""
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