"""Test OWASP Pro rules."""

from pathlib import Path

from apiposture.core.models.endpoint import Endpoint
from apiposture.core.models.enums import (
    EndpointType,
    Framework,
    HttpMethod,
    SecurityClassification,
    Severity,
)

from apiposture_pro.rules.base import _keyword_match
from apiposture_pro.rules.owasp.ap009_broken_auth import AP009BrokenAuthentication
from apiposture_pro.rules.owasp.ap010_injection_vectors import AP010InjectionVectors
from apiposture_pro.rules.owasp.ap011_broken_access_control import (
    AP011BrokenAccessControl,
)
from apiposture_pro.rules.owasp.ap012_security_misconfiguration import (
    AP012SecurityMisconfiguration,
)
from apiposture_pro.rules.owasp.ap013_vulnerable_components import (
    AP013VulnerableComponents,
)


def test_ap009_broken_auth_write_without_auth() -> None:
    """Test AP009 detects write operations without authentication."""
    rule = AP009BrokenAuthentication()

    endpoint = Endpoint(
        route="/api/users",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="create_user",
        classification=SecurityClassification.PUBLIC,
    )

    findings = list(rule.evaluate(endpoint))
    assert len(findings) > 0
    assert findings[0].rule_id == "AP009"
    assert "write" in findings[0].message.lower() or "POST" in findings[0].message


def test_ap009_auth_endpoint_with_get() -> None:
    """Test AP009 detects authentication endpoints accepting GET."""
    rule = AP009BrokenAuthentication()

    endpoint = Endpoint(
        route="/api/login",
        methods=[HttpMethod.GET, HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=20,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="login",
    )

    findings = list(rule.evaluate(endpoint))
    assert len(findings) > 0
    assert any("GET" in f.message for f in findings)


def test_ap009_rate_limiting_on_login() -> None:
    """Test AP009 flags login endpoints without rate limiting indication."""
    rule = AP009BrokenAuthentication()

    endpoint = Endpoint(
        route="/api/login",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="login",
    )

    findings = list(rule.evaluate(endpoint))
    assert any("rate limit" in f.message.lower() for f in findings)


def test_ap009_rate_limiting_not_flagged_with_hint() -> None:
    """Test AP009 does not flag login with rate_limit in function name."""
    rule = AP009BrokenAuthentication()

    endpoint = Endpoint(
        route="/api/login",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="login_rate_limited",
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("rate limit" in f.message.lower() for f in findings)


def test_ap009_password_change_public() -> None:
    """Test AP009 flags public password change endpoints."""
    rule = AP009BrokenAuthentication()

    endpoint = Endpoint(
        route="/api/password",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="change_password",
        classification=SecurityClassification.PUBLIC,
    )

    findings = list(rule.evaluate(endpoint))
    assert any("password change" in f.message.lower() for f in findings)


def test_ap009_registration_without_rate_limit() -> None:
    """Test AP009 flags public registration without rate limiting."""
    rule = AP009BrokenAuthentication()

    endpoint = Endpoint(
        route="/api/register",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="register",
        classification=SecurityClassification.PUBLIC,
    )

    findings = list(rule.evaluate(endpoint))
    assert any("registration" in f.message.lower() for f in findings)


def test_ap009_register_keyword_detected() -> None:
    """Test AP009 detects 'register' as an auth keyword."""
    rule = AP009BrokenAuthentication()

    endpoint = Endpoint(
        route="/api/register",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="register",
    )

    findings = list(rule.evaluate(endpoint))
    assert any("GET" in f.message for f in findings)


def test_ap010_injection_file_path_param() -> None:
    """Test AP010 detects file path parameters."""
    rule = AP010InjectionVectors()

    endpoint = Endpoint(
        route="/api/download/{filename}",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=30,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="download_file",
    )

    findings = list(rule.evaluate(endpoint))
    assert len(findings) > 0
    assert any("path traversal" in f.message.lower() for f in findings)


def test_ap010_query_endpoint() -> None:
    """Test AP010 detects query endpoints."""
    rule = AP010InjectionVectors()

    endpoint = Endpoint(
        route="/api/search",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=40,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="search_users",
    )

    findings = list(rule.evaluate(endpoint))
    assert len(findings) > 0
    assert any("injection" in f.message.lower() for f in findings)


def test_ap010_command_execution() -> None:
    """Test AP010 detects command execution endpoints."""
    rule = AP010InjectionVectors()

    endpoint = Endpoint(
        route="/api/execute",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=50,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="execute_command",
    )

    findings = list(rule.evaluate(endpoint))
    assert len(findings) > 0
    assert any("command injection" in f.message.lower() for f in findings)


def test_ap010_ssrf_detection() -> None:
    """Test AP010 detects SSRF via URL parameters."""
    rule = AP010InjectionVectors()

    endpoint = Endpoint(
        route="/api/fetch/{url}",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="fetch_url",
    )

    findings = list(rule.evaluate(endpoint))
    assert any("SSRF" in f.message for f in findings)


def test_ap010_ssrf_webhook_param() -> None:
    """Test AP010 detects SSRF via webhook parameter."""
    rule = AP010InjectionVectors()

    endpoint = Endpoint(
        route="/api/notify/{webhook}",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="notify",
    )

    findings = list(rule.evaluate(endpoint))
    assert any("SSRF" in f.message for f in findings)


def test_ap010_render_template_no_false_positive() -> None:
    """Test AP010 does NOT flag render_template (Flask safe pattern)."""
    rule = AP010InjectionVectors()

    endpoint = Endpoint(
        route="/api/page",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FLASK,
        endpoint_type=EndpointType.FUNCTION,
        function_name="render_template_page",
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("template injection" in f.message.lower() for f in findings)


def test_ap010_orm_query_no_false_positive() -> None:
    """Test AP010 does NOT flag ORM query endpoints."""
    rule = AP010InjectionVectors()

    endpoint = Endpoint(
        route="/api/users",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.DJANGO_DRF,
        endpoint_type=EndpointType.FUNCTION,
        function_name="orm_search_users",
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("injection" in f.message.lower() for f in findings)


def test_ap010_redirect_reduced_keywords() -> None:
    """Test AP010 open redirect uses reduced keyword set."""
    rule = AP010InjectionVectors()

    # "return" should no longer trigger open redirect
    endpoint = Endpoint(
        route="/api/data/{id}",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="return_data",
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("open redirect" in f.message.lower() for f in findings)


def test_ap010_header_injection() -> None:
    """Test AP010 detects header injection."""
    rule = AP010InjectionVectors()

    endpoint = Endpoint(
        route="/api/response",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="set_header_value",
    )

    findings = list(rule.evaluate(endpoint))
    assert any("header injection" in f.message.lower() for f in findings)


def test_ap010_log_injection() -> None:
    """Test AP010 detects log injection."""
    rule = AP010InjectionVectors()

    endpoint = Endpoint(
        route="/api/audit/{event_id}",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="log_event",
    )

    findings = list(rule.evaluate(endpoint))
    assert any("log injection" in f.message.lower() for f in findings)


def test_ap011_idor_vulnerability() -> None:
    """Test AP011 detects IDOR vulnerabilities."""
    rule = AP011BrokenAccessControl()

    endpoint = Endpoint(
        route="/api/users/{id}",
        methods=[HttpMethod.PUT],
        file_path=Path("api.py"),
        line_number=60,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="update_user",
    )
    endpoint.authorization.requires_auth = True

    findings = list(rule.evaluate(endpoint))
    assert len(findings) > 0
    assert any("IDOR" in f.message for f in findings)


def test_ap011_admin_endpoint_without_role() -> None:
    """Test AP011 detects admin endpoints without role checks."""
    rule = AP011BrokenAccessControl()

    endpoint = Endpoint(
        route="/api/admin/delete",
        methods=[HttpMethod.DELETE],
        file_path=Path("api.py"),
        line_number=70,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="admin_delete",
    )

    findings = list(rule.evaluate(endpoint))
    assert len(findings) > 0
    assert any("administrative" in f.message.lower() for f in findings)


def test_ap011_delete_not_flagged_without_admin_context() -> None:
    """Test AP011 does NOT flag 'delete' without admin context."""
    rule = AP011BrokenAccessControl()

    endpoint = Endpoint(
        route="/api/users/{id}",
        methods=[HttpMethod.DELETE],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="delete_my_account",
    )

    findings = list(rule.evaluate(endpoint))
    # Should NOT get an "administrative" finding for non-admin delete
    assert not any(
        "administrative" in f.message.lower() and "role" in f.message.lower()
        for f in findings
    )


def test_ap011_list_no_false_positive() -> None:
    """Test AP011 does NOT flag 'list' endpoints as bulk operations."""
    rule = AP011BrokenAccessControl()

    endpoint = Endpoint(
        route="/api/items",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="list_items",
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("bulk" in f.message.lower() for f in findings)


def test_ap011_extended_id_params() -> None:
    """Test AP011 detects extended ID parameter patterns (uuid, slug, etc)."""
    rule = AP011BrokenAccessControl()

    for param in ["{uuid}", "{slug}", "{record_id}", "{item_id}", "{order_id}"]:
        endpoint = Endpoint(
            route=f"/api/users/{param}",
            methods=[HttpMethod.PUT],
            file_path=Path("api.py"),
            line_number=10,
            framework=Framework.FASTAPI,
            endpoint_type=EndpointType.ROUTER,
            function_name="update_user",
        )
        endpoint.authorization.requires_auth = True

        findings = list(rule.evaluate(endpoint))
        assert any("IDOR" in f.message for f in findings), f"Failed for param {param}"


def test_ap011_tenant_isolation() -> None:
    """Test AP011 detects tenant isolation issues."""
    rule = AP011BrokenAccessControl()

    endpoint = Endpoint(
        route="/api/{tenant_id}/data",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="get_tenant_data",
    )

    findings = list(rule.evaluate(endpoint))
    assert any("tenant" in f.message.lower() for f in findings)


def test_ap012_debug_endpoint() -> None:
    """Test AP012 detects debug endpoints."""
    rule = AP012SecurityMisconfiguration()

    endpoint = Endpoint(
        route="/api/debug",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=80,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="debug_info",
        classification=SecurityClassification.PUBLIC,
    )

    findings = list(rule.evaluate(endpoint))
    assert len(findings) > 0
    assert any("debug" in f.message.lower() for f in findings)


def test_ap012_config_endpoint() -> None:
    """Test AP012 detects configuration endpoints."""
    rule = AP012SecurityMisconfiguration()

    endpoint = Endpoint(
        route="/api/config",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=90,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="get_config",
        classification=SecurityClassification.PUBLIC,
    )

    findings = list(rule.evaluate(endpoint))
    assert len(findings) > 0
    assert any("configuration" in f.message.lower() for f in findings)


def test_ap012_health_endpoint_info_severity() -> None:
    """Test AP012 flags health endpoint at INFO severity, not HIGH."""
    rule = AP012SecurityMisconfiguration()

    endpoint = Endpoint(
        route="/api/health",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="health_check",
        classification=SecurityClassification.PUBLIC,
    )

    findings = list(rule.evaluate(endpoint))
    health_findings = [f for f in findings if "health" in f.message.lower() or "status" in f.message.lower()]
    assert len(health_findings) > 0
    # Should be INFO, not LOW or HIGH
    assert all(f.severity == Severity.INFO for f in health_findings)


def test_ap012_docs_endpoint_info_severity() -> None:
    """Test AP012 flags docs endpoint at INFO severity."""
    rule = AP012SecurityMisconfiguration()

    endpoint = Endpoint(
        route="/docs",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="swagger_ui",
        classification=SecurityClassification.PUBLIC,
    )

    findings = list(rule.evaluate(endpoint))
    docs_findings = [f for f in findings if "documentation" in f.message.lower()]
    assert len(docs_findings) > 0
    assert all(f.severity == Severity.INFO for f in docs_findings)


def test_ap012_test_keyword_reduced_false_positive() -> None:
    """Test AP012 does NOT flag 'test' in function name (only route prefix)."""
    rule = AP012SecurityMisconfiguration()

    endpoint = Endpoint(
        route="/api/users",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="test_connection",
        classification=SecurityClassification.PUBLIC,
    )

    findings = list(rule.evaluate(endpoint))
    # Should NOT flag as debug/test since route is /api/users, not /test/...
    assert not any("debug/test" in f.message.lower() for f in findings)


def test_ap012_cors_detection() -> None:
    """Test AP012 detects CORS-related endpoints."""
    rule = AP012SecurityMisconfiguration()

    endpoint = Endpoint(
        route="/api/data",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="cors_handler",
    )

    findings = list(rule.evaluate(endpoint))
    assert any("CORS" in f.message for f in findings)


def test_ap012_catchall_route() -> None:
    """Test AP012 detects catch-all routes."""
    rule = AP012SecurityMisconfiguration()

    endpoint = Endpoint(
        route="/{path:path}",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="catch_all",
    )

    findings = list(rule.evaluate(endpoint))
    assert any("catch-all" in f.message.lower() for f in findings)


def test_ap013_deprecated_auth() -> None:
    """Test AP013 detects deprecated authentication methods."""
    rule = AP013VulnerableComponents()

    endpoint = Endpoint(
        route="/api/auth",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=100,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="basic_auth_login",
    )

    findings = list(rule.evaluate(endpoint))
    assert len(findings) > 0
    assert any("deprecated" in f.message.lower() for f in findings)


def test_ap013_xml_processing() -> None:
    """Test AP013 detects XML processing endpoints."""
    rule = AP013VulnerableComponents()

    endpoint = Endpoint(
        route="/api/xml",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=110,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="process_xml",
    )

    findings = list(rule.evaluate(endpoint))
    assert len(findings) > 0
    assert any("XXE" in f.message or "xml" in f.message.lower() for f in findings)


def test_ap013_deserialization() -> None:
    """Test AP013 detects unsafe deserialization."""
    rule = AP013VulnerableComponents()

    endpoint = Endpoint(
        route="/api/data",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=120,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="deserialize_data",
    )

    findings = list(rule.evaluate(endpoint))
    assert len(findings) > 0
    assert any("deserialization" in f.message.lower() for f in findings)


def test_ap013_loads_no_false_positive() -> None:
    """Test AP013 does NOT flag 'loads' (json.loads is safe)."""
    rule = AP013VulnerableComponents()

    endpoint = Endpoint(
        route="/api/data",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="json_loads_data",
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("deserialization" in f.message.lower() for f in findings)


def test_ap013_match_no_false_positive() -> None:
    """Test AP013 does NOT flag 'match' or 'validate' as ReDoS."""
    rule = AP013VulnerableComponents()

    endpoint = Endpoint(
        route="/api/validate",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="validate_email",
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("ReDoS" in f.message for f in findings)


def test_ap013_id_keyword_no_false_positive() -> None:
    """Test AP013 does NOT flag 'id' keyword for random generation."""
    rule = AP013VulnerableComponents()

    endpoint = Endpoint(
        route="/api/items",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="create_item_with_id",
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("random" in f.message.lower() for f in findings)


def test_ap013_weak_ssl_detection() -> None:
    """Test AP013 detects weak SSL/TLS configuration."""
    rule = AP013VulnerableComponents()

    endpoint = Endpoint(
        route="/api/connect",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="ssl_no_verify_connect",
    )

    findings = list(rule.evaluate(endpoint))
    assert any("SSL/TLS" in f.message for f in findings)


def test_ap013_instance_state_not_shared() -> None:
    """Test AP013 uses instance state, not class-level state."""
    rule1 = AP013VulnerableComponents()
    rule2 = AP013VulnerableComponents()

    endpoint = Endpoint(
        route="/api/data",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="random_token_gen",
    )

    list(rule1.evaluate(endpoint))
    # rule2 should still report the same finding (separate instance)
    findings2 = list(rule2.evaluate(endpoint))
    assert any("random" in f.message.lower() for f in findings2)


# ======================================================================
# New tests for false positive fixes discovered via real-world scanning
# ======================================================================


def test_ap009_skips_test_directory_endpoints() -> None:
    """Test AP009 does NOT flag endpoints in test/example directories."""
    rule = AP009BrokenAuthentication()

    endpoint = Endpoint(
        route="/createapiview/",
        methods=[HttpMethod.POST],
        file_path=Path("tests/schemas/views.py"),
        line_number=10,
        framework=Framework.DJANGO_DRF,
        endpoint_type=EndpointType.CONTROLLER_ACTION,
        function_name="post",
        classification=SecurityClassification.PUBLIC,
    )

    findings = list(rule.evaluate(endpoint))
    assert len(findings) == 0


def test_ap009_skips_examples_directory_endpoints() -> None:
    """Test AP009 does NOT flag endpoints in examples/ directory."""
    rule = AP009BrokenAuthentication()

    endpoint = Endpoint(
        route="/tasks/add",
        methods=[HttpMethod.POST],
        file_path=Path("examples/celery/tasks.py"),
        line_number=10,
        framework=Framework.FLASK,
        endpoint_type=EndpointType.FUNCTION,
        function_name="add",
        classification=SecurityClassification.PUBLIC,
    )

    findings = list(rule.evaluate(endpoint))
    assert len(findings) == 0


def test_ap009_still_flags_production_endpoints() -> None:
    """Test AP009 still flags production write endpoints without auth."""
    rule = AP009BrokenAuthentication()

    endpoint = Endpoint(
        route="/api/users",
        methods=[HttpMethod.POST],
        file_path=Path("src/app/routes.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="create_user",
        classification=SecurityClassification.PUBLIC,
    )

    findings = list(rule.evaluate(endpoint))
    assert len(findings) > 0


def test_ap010_process_no_false_positive() -> None:
    """Test AP010 does NOT flag 'process' as command injection (too generic)."""
    rule = AP010InjectionVectors()

    endpoint = Endpoint(
        route="/tasks/process",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FLASK,
        endpoint_type=EndpointType.FUNCTION,
        function_name="process",
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("command injection" in f.message.lower() for f in findings)


def test_ap010_return_template_no_false_positive() -> None:
    """Test AP010 does NOT flag return_template as template injection."""
    rule = AP010InjectionVectors()

    endpoint = Endpoint(
        route="/template",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FLASK,
        endpoint_type=EndpointType.FUNCTION,
        function_name="return_template",
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("template injection" in f.message.lower() for f in findings)


def test_ap010_missing_template_no_false_positive() -> None:
    """Test AP010 does NOT flag missing_template as template injection."""
    rule = AP010InjectionVectors()

    endpoint = Endpoint(
        route="/missing",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FLASK,
        endpoint_type=EndpointType.FUNCTION,
        function_name="missing_template",
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("template injection" in f.message.lower() for f in findings)


def test_ap010_jquery_route_no_false_positive() -> None:
    """Test AP010 does NOT flag 'jquery' in route as SQL injection (substring of 'query')."""
    rule = AP010InjectionVectors()

    endpoint = Endpoint(
        route="/<any(xhr, jquery, fetch):js>",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FLASK,
        endpoint_type=EndpointType.FUNCTION,
        function_name="index",
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("injection" in f.message.lower() and "query" in f.message.lower() for f in findings)


def test_ap010_still_flags_real_query_endpoint() -> None:
    """Test AP010 still flags actual query/search endpoints."""
    rule = AP010InjectionVectors()

    endpoint = Endpoint(
        route="/api/query",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="run_query",
    )

    findings = list(rule.evaluate(endpoint))
    assert any("injection" in f.message.lower() for f in findings)


def test_ap011_idor_skipped_with_specific_requirements() -> None:
    """Test AP011 does NOT flag IDOR when has_specific_requirements is True."""
    rule = AP011BrokenAccessControl()

    endpoint = Endpoint(
        route="/users/{user_id}",
        methods=[HttpMethod.PATCH],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="update_user",
    )
    endpoint.authorization.requires_auth = True
    # Simulate Depends(get_current_active_superuser) detection
    endpoint.authorization.permissions = ["get_current_active_superuser"]

    findings = list(rule.evaluate(endpoint))
    assert not any("IDOR" in f.message for f in findings)


def test_ap010_profile_route_no_path_traversal_fp() -> None:
    """Test AP010 does NOT flag /api/users/{id}/profile as path traversal."""
    rule = AP010InjectionVectors()

    endpoint = Endpoint(
        route="/api/users/{id}/profile",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="get_user_profile",
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("path traversal" in f.message.lower() for f in findings)


def test_ap010_catalog_no_log_injection_fp() -> None:
    """Test AP010 does NOT flag 'get_catalog' as log injection."""
    rule = AP010InjectionVectors()

    endpoint = Endpoint(
        route="/api/catalog/{id}",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="get_catalog",
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("log injection" in f.message.lower() for f in findings)


def test_ap010_blog_no_log_injection_fp() -> None:
    """Test AP010 does NOT flag '/blog/{slug}' as log injection."""
    rule = AP010InjectionVectors()

    endpoint = Endpoint(
        route="/blog/{slug}",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="get_blog_post",
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("log injection" in f.message.lower() for f in findings)


def test_ap010_file_route_still_triggers_path_traversal() -> None:
    """Test AP010 still flags /api/file/{name} as path traversal."""
    rule = AP010InjectionVectors()

    endpoint = Endpoint(
        route="/api/file/{name}",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="get_file",
    )

    findings = list(rule.evaluate(endpoint))
    assert any("path traversal" in f.message.lower() for f in findings)


def test_ap010_log_event_still_triggers() -> None:
    """Test AP010 still flags 'log_event' as log injection."""
    rule = AP010InjectionVectors()

    endpoint = Endpoint(
        route="/api/audit/{event_id}",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="log_event",
    )

    findings = list(rule.evaluate(endpoint))
    assert any("log injection" in f.message.lower() for f in findings)


def test_ap011_user_manager_no_admin_fp() -> None:
    """Test AP011 does NOT flag 'user_manager' as admin endpoint."""
    rule = AP011BrokenAccessControl()

    endpoint = Endpoint(
        route="/api/users",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="user_manager",
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("administrative" in f.message.lower() for f in findings)


def test_ap012_device_route_no_debug_fp() -> None:
    """Test AP012 does NOT flag /api/device/{id} as debug endpoint."""
    rule = AP012SecurityMisconfiguration()

    endpoint = Endpoint(
        route="/api/device/{id}",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="get_device",
        classification=SecurityClassification.PUBLIC,
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("debug/test" in f.message.lower() for f in findings)


def test_ap012_update_profile_no_upload_fp() -> None:
    """Test AP012 does NOT flag 'update_profile' as file upload."""
    rule = AP012SecurityMisconfiguration()

    endpoint = Endpoint(
        route="/api/users/{id}/profile",
        methods=[HttpMethod.PUT],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="update_profile",
        classification=SecurityClassification.PUBLIC,
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("file upload" in f.message.lower() for f in findings)


def test_ap012_optional_fields_no_cors_fp() -> None:
    """Test AP012 does NOT flag 'get_optional_fields' as CORS endpoint."""
    rule = AP012SecurityMisconfiguration()

    endpoint = Endpoint(
        route="/api/fields",
        methods=[HttpMethod.GET],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="get_optional_fields",
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("CORS" in f.message for f in findings)


def test_ap013_pattern_recognition_no_redos_fp() -> None:
    """Test AP013 does NOT flag 'pattern_recognition' as ReDoS."""
    rule = AP013VulnerableComponents()

    endpoint = Endpoint(
        route="/api/recognize",
        methods=[HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FASTAPI,
        endpoint_type=EndpointType.ROUTER,
        function_name="pattern_recognition",
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("ReDoS" in f.message for f in findings)


def test_ap012_reset_form_not_flagged_as_admin() -> None:
    """Test AP012 does NOT flag /reset/<token> as admin operation."""
    rule = AP012SecurityMisconfiguration()

    endpoint = Endpoint(
        route="/reset/<token>",
        methods=[HttpMethod.GET, HttpMethod.POST],
        file_path=Path("api.py"),
        line_number=10,
        framework=Framework.FLASK,
        endpoint_type=EndpointType.FUNCTION,
        function_name="reset_form",
        classification=SecurityClassification.PUBLIC,
    )

    findings = list(rule.evaluate(endpoint))
    assert not any("administrative" in f.message.lower() for f in findings)


# ======================================================================
# Unit tests for _keyword_match utility
# ======================================================================


def test_keyword_match_snake_case() -> None:
    """Test _keyword_match matches keywords in snake_case."""
    assert _keyword_match("log_event", "log")
    assert _keyword_match("get_file_name", "file")
    assert _keyword_match("rotate_key", "key")


def test_keyword_match_route_segments() -> None:
    """Test _keyword_match matches keywords in route segments."""
    assert _keyword_match("/api/file/{name}", "file")
    assert _keyword_match("/api/log/{id}", "log")
    assert _keyword_match("/manage/users", "manage")


def test_keyword_match_start_and_end() -> None:
    """Test _keyword_match matches at string start and end."""
    assert _keyword_match("log", "log")
    assert _keyword_match("dev", "dev")
    assert _keyword_match("file_upload", "file")
    assert _keyword_match("upload_file", "file")


def test_keyword_match_no_substring() -> None:
    """Test _keyword_match does NOT match substrings."""
    assert not _keyword_match("profile", "file")
    assert not _keyword_match("catalog", "log")
    assert not _keyword_match("blog", "log")
    assert not _keyword_match("dialog", "log")
    assert not _keyword_match("monkey", "key")
    assert not _keyword_match("keyboard", "key")
    assert not _keyword_match("turkey", "key")
    assert not _keyword_match("device", "dev")
    assert not _keyword_match("developer", "dev")
    assert not _keyword_match("manager", "manage")
    assert not _keyword_match("management", "manage")
    assert not _keyword_match("optional", "options")


def test_keyword_match_dot_separator() -> None:
    """Test _keyword_match matches with dot separator."""
    assert _keyword_match("app.dev.settings", "dev")
    assert _keyword_match("config.log.level", "log")


def test_keyword_match_hyphen_separator() -> None:
    """Test _keyword_match matches with hyphen separator."""
    assert _keyword_match("api-key-rotation", "key")
    assert _keyword_match("dev-server", "dev")
