diff --git a/spond/base.py b/spond/base.py index 872e4c3..16bc1e3 100644 --- a/spond/base.py +++ b/spond/base.py @@ -8,6 +8,7 @@ Not intended to be instantiated directly — use a subclass. """ +import functools from abc import ABC from collections.abc import Callable @@ -65,6 +66,7 @@ def require_authentication(func: Callable): client is not yet authenticated. On `AuthenticationError`, closes the underlying aiohttp session before re-raising.""" + @functools.wraps(func) async def wrapper(self, *args, **kwargs): if not self.token: try: diff --git a/tests/test_spond.py b/tests/test_spond.py index 7ab7c7b..8c99583 100644 --- a/tests/test_spond.py +++ b/tests/test_spond.py @@ -524,3 +524,32 @@ async def test_login__error_response_raises(self, mock_post) -> None: with pytest.raises(AuthenticationError): await s.login() assert s.token is None + + +class TestRequireAuthenticationDecorator: + """The `require_authentication` decorator must preserve the wrapped + method's metadata (signature, docstring, name) so `inspect`-based + tools — pdoc, IDE help, tab completion — see the real method + rather than the wrapper's `(*args, **kwargs)` shim. + """ + + def test_decorator_preserves_signature(self) -> None: + """Decorated methods must expose their real parameter list.""" + import inspect + + # `get_posts` is decorated and has a distinctive signature + params = list(inspect.signature(Spond.get_posts).parameters) + assert params == ["self", "group_id", "max_posts", "include_comments"] + + def test_decorator_preserves_docstring(self) -> None: + """Decorated methods must expose their own docstring, not the + wrapper's.""" + import inspect + + doc = inspect.getdoc(Spond.get_profile) or "" + # Wrapper docstring would start with 'Decorator that...' if leaked. + assert "Retrieve the authenticated user's profile." in doc + + def test_decorator_preserves_name(self) -> None: + """`__name__` must be the method's, not 'wrapper'.""" + assert Spond.get_events.__name__ == "get_events"