"""Regression test: /api/start_research carries the @api_rate_limit decorator (#3135).

``start_research`` is the primary research-submission endpoint, so it must be
rate-limited (per-user, via ``@api_rate_limit``) — otherwise a single account
can flood the research queue.

This is a *static* check of the source: it parses research_routes.py and
asserts ``start_research`` is decorated with ``api_rate_limit``. Two earlier
approaches were abandoned as flaky in the full-suite CI run — both a
request-volume integration test and an introspection of
``limiter.limit_manager`` depend on the process-wide Flask-Limiter singleton,
whose enabled state and decorated-limit registry are mutated by other tests
(they pass in isolation but flake in the consolidated run). The AST check is
deterministic and guards exactly the regression we care about: someone dropping
``@api_rate_limit``. The limiter's runtime enforcement/exempt logic is covered
by ``tests/security/test_rate_limiter.py``.
"""

import ast
from pathlib import Path


def _decorator_names(func_node):
    """Return the simple names of every decorator on a function node.

    Handles ``@name``, ``@obj.name`` and the call forms ``@name(...)`` /
    ``@obj.name(...)`` so e.g. ``@require_json_body(...)`` yields
    ``require_json_body``.
    """
    names = []
    for dec in func_node.decorator_list:
        target = dec.func if isinstance(dec, ast.Call) else dec
        if isinstance(target, ast.Name):
            names.append(target.id)
        elif isinstance(target, ast.Attribute):
            names.append(target.attr)
    return names


def test_start_research_decorated_with_api_rate_limit():
    import local_deep_research.web.routes.research_routes as research_routes

    source = Path(research_routes.__file__).read_text()
    tree = ast.parse(source)

    func = next(
        (
            node
            for node in ast.walk(tree)
            if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
            and node.name == "start_research"
        ),
        None,
    )
    assert func is not None, (
        "start_research view not found in research_routes.py"
    )

    decorators = _decorator_names(func)
    assert "api_rate_limit" in decorators, (
        "start_research must be decorated with @api_rate_limit so research "
        f"submissions are rate limited; found decorators: {decorators}"
    )
