# allow: no-sut-import — guardian; AST-parses news_routes.py source to assert rate-limit decorators are present
"""
Tests for rate limiting decorators on news API routes.

Verifies that the PR adds rate limiting to the four expensive POST endpoints
in news_routes.py using AST parsing to avoid heavy import dependencies.
"""

import ast
from pathlib import Path

# Path to the news_routes module source
_NEWS_ROUTES_PATH = (
    Path(__file__).resolve().parents[2]
    / "src"
    / "local_deep_research"
    / "web"
    / "routes"
    / "news_routes.py"
)


def _parse_source():
    """Read and parse the news_routes.py source file."""
    return ast.parse(_NEWS_ROUTES_PATH.read_text())


def _get_module_source():
    """Read the news_routes.py source as text."""
    return _NEWS_ROUTES_PATH.read_text()


def _get_function_decorators(tree):
    """Extract a mapping of function name -> list of decorator names."""
    result = {}
    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            dec_names = []
            for dec in node.decorator_list:
                if isinstance(dec, ast.Name):
                    dec_names.append(dec.id)
                elif isinstance(dec, ast.Call):
                    if isinstance(dec.func, ast.Attribute):
                        dec_names.append(
                            f"{dec.func.value.id}.{dec.func.attr}"
                            if isinstance(dec.func.value, ast.Name)
                            else dec.func.attr
                        )
                    elif isinstance(dec.func, ast.Name):
                        dec_names.append(dec.func.id)
            result[node.name] = dec_names
    return result


def _get_module_level_assignments(tree):
    """Extract module-level variable assignments as {name: ast node}."""
    result = {}
    for node in ast.iter_child_nodes(tree):
        if isinstance(node, ast.Assign):
            for target in node.targets:
                if isinstance(target, ast.Name):
                    result[target.id] = node.value
    return result


class TestNewsRateLimitingDeclarations:
    """Test that shared rate limit objects are declared at module level."""

    def test_news_create_limit_declared(self):
        tree = _parse_source()
        assignments = _get_module_level_assignments(tree)
        assert "_news_create_limit" in assignments

    def test_news_research_limit_declared(self):
        tree = _parse_source()
        assignments = _get_module_level_assignments(tree)
        assert "_news_research_limit" in assignments

    def test_news_feedback_limit_declared(self):
        tree = _parse_source()
        assignments = _get_module_level_assignments(tree)
        assert "_news_feedback_limit" in assignments

    def test_news_preferences_limit_declared(self):
        tree = _parse_source()
        assignments = _get_module_level_assignments(tree)
        assert "_news_preferences_limit" in assignments


class TestNewsRateLimitingDecorators:
    """Test that rate limit decorators are applied to the correct routes."""

    def test_create_subscription_has_rate_limit_decorator(self):
        tree = _parse_source()
        decorators = _get_function_decorators(tree)
        assert "_news_create_limit" in decorators.get(
            "create_subscription", []
        ), "create_subscription must have @_news_create_limit decorator"

    def test_submit_feedback_has_rate_limit_decorator(self):
        tree = _parse_source()
        decorators = _get_function_decorators(tree)
        assert "_news_feedback_limit" in decorators.get(
            "submit_feedback", []
        ), "submit_feedback must have @_news_feedback_limit decorator"

    def test_research_news_item_has_rate_limit_decorator(self):
        tree = _parse_source()
        decorators = _get_function_decorators(tree)
        assert "_news_research_limit" in decorators.get(
            "research_news_item", []
        ), "research_news_item must have @_news_research_limit decorator"

    def test_save_preferences_has_rate_limit_decorator(self):
        tree = _parse_source()
        decorators = _get_function_decorators(tree)
        assert "_news_preferences_limit" in decorators.get(
            "save_preferences", []
        ), "save_preferences must have @_news_preferences_limit decorator"


class TestNewsRateLimitValues:
    """Test that rate limit values are reasonable."""

    def test_rate_limiter_imported(self):
        source = _get_module_source()
        assert "from ...security.rate_limiter import limiter" in source

    def test_create_limit_value(self):
        source = _get_module_source()
        assert '"10 per minute"' in source
        assert 'scope="news_create"' in source

    def test_research_limit_is_most_restrictive(self):
        """Research triggers AI work so should have the lowest limit."""
        source = _get_module_source()
        assert '"5 per minute"' in source
        assert 'scope="news_research"' in source

    def test_feedback_limit_is_most_permissive(self):
        """Feedback is lightweight so can allow more requests."""
        source = _get_module_source()
        assert '"30 per minute"' in source
        assert 'scope="news_feedback"' in source

    def test_preferences_limit_value(self):
        source = _get_module_source()
        # preferences uses same rate as create (10 per minute)
        assert 'scope="news_preferences"' in source

    def test_uses_shared_limit(self):
        """Verify shared_limit is used (not per-route limit)."""
        source = _get_module_source()
        assert "limiter.shared_limit" in source


class TestUnlimitedEndpoints:
    """Verify that read-only GET endpoints do NOT have rate limits."""

    def test_get_news_feed_not_rate_limited(self):
        tree = _parse_source()
        decorators = _get_function_decorators(tree)
        feed_decs = decorators.get("get_news_feed", [])
        assert not any(d.startswith("_news_") for d in feed_decs), (
            "GET /feed should not have a news-specific rate limit"
        )

    def test_get_subscriptions_not_rate_limited(self):
        tree = _parse_source()
        decorators = _get_function_decorators(tree)
        decs = decorators.get("get_subscriptions", [])
        assert not any(d.startswith("_news_") for d in decs), (
            "GET /subscriptions should not have a news-specific rate limit"
        )

    def test_get_categories_not_rate_limited(self):
        tree = _parse_source()
        decorators = _get_function_decorators(tree)
        decs = decorators.get("get_categories", [])
        assert not any(d.startswith("_news_") for d in decs), (
            "GET /categories should not have a news-specific rate limit"
        )
