Files
cleveragents-core/features/steps/auth_refresh.py
HAL9000 947c5a682b
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 52s
CI / build (pull_request) Successful in 59s
CI / helm (pull_request) Successful in 26s
CI / quality (pull_request) Successful in 1m14s
CI / security (pull_request) Successful in 1m41s
CI / push-validation (pull_request) Successful in 40s
CI / typecheck (pull_request) Successful in 1m47s
CI / e2e_tests (pull_request) Failing after 4m20s
CI / integration_tests (pull_request) Failing after 4m37s
CI / unit_tests (pull_request) Failing after 4m52s
CI / coverage (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / status-check (pull_request) Failing after 4s
style(auth): apply ruff format to JWT files
Fix CI lint failure caused by ruff format check finding 3 files that
needed reformatting: features/steps/auth_refresh.py, src/cleveragents/a2a/asgi.py,
and src/cleveragents/core/jwt_service.py. No logic changes — pure formatting
(import grouping, blank lines between top-level functions, line wrapping).
2026-05-05 17:52:16 +00:00

198 lines
6.8 KiB
Python

"""Step definitions for JWT refresh endpoint BDD scenarios.
This module implements steps that mimic the behaviour of the ASGI
auth/refresh endpoint using the ``cleveragents`` token helpers directly
instead of firing real HTTP requests. It lets tests assert on the
Python API surface (status codes, JSON bodies, token introspection)
without needing a live server process.
"""
from __future__ import annotations
import json
from typing import TYPE_CHECKING
from behave import given, then, when
if TYPE_CHECKING:
from behave.runner import Context
from cleveragents.core.jwt_service import (
create_access_token,
create_refresh_token,
decode_token,
)
# ---------------------------------------------------------------------------
# Given
# ---------------------------------------------------------------------------
@given("the application is running")
def step_app_running(context: Context) -> None:
"""Application is assumed to be running for testing."""
pass
@given("I have a valid refresh token")
def step_valid_refresh_token(context: Context) -> None:
"""Generate and store a valid refresh token."""
context.refresh_token = create_refresh_token("test-user")
# ---------------------------------------------------------------------------
# When
# ---------------------------------------------------------------------------
@when('I send a POST request to "/auth/refresh" with JSON body:')
def step_send_post_auth_refresh_json(context: Context) -> None:
"""Send a POST request to the auth refresh endpoint with a JSON body.
The multi-line string in the Gherkin step becomes ``context.text``.
"""
try:
body = json.loads(context.text)
except (json.JSONDecodeError, TypeError):
body = None
context.request_body = body
context.request_content_type = "application/json"
if body is None:
context.response_status = 400
context.response_data = {"error": "Invalid JSON body"}
else:
_process_refresh_request(context, body)
@when(
'I send a POST request to "/auth/refresh" with content type "{content_type}" and body:'
)
def step_send_post_auth_refresh_content_type(
context: Context, content_type: str
) -> None:
"""Send a POST request with a specific content type."""
context.request_content_type = content_type
context.request_body = context.text
if "application/json" not in content_type:
context.response_status = 400
context.response_data = {"error": "Content-Type must be application/json"}
else:
try:
body = json.loads(context.text)
context.request_body = body
_process_refresh_request(context, body)
except json.JSONDecodeError:
context.response_status = 400
context.response_data = {"error": "Invalid JSON body"}
@when(
'I send a POST request to "/auth/refresh" with JSON body containing a non-refresh token'
)
def step_send_non_refresh_token(context: Context) -> None:
"""Send an access token instead of a refresh token."""
context.request_body = {"refresh_token": create_access_token("test-user")}
_process_refresh_request(context, context.request_body)
@when('I send a POST request to "/auth/refresh" with the stored refresh token')
def step_send_stored_refresh_token(context: Context) -> None:
"""Send a POST request using the refresh token stored in context."""
body = {"refresh_token": context.refresh_token}
context.request_body = body
context.request_content_type = "application/json"
_process_refresh_request(context, body)
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _process_refresh_request(context: Context, body: object) -> None:
"""Execute the refresh-token flow and capture the result on *context*.
Args:
context: The Behave context object.
body: The parsed JSON body (must be a ``dict``).
"""
from cleveragents.core.jwt_service import refresh_tokens
refresh_token_val: object = (
body.get("refresh_token") if isinstance(body, dict) else body
)
if not refresh_token_val or not isinstance(refresh_token_val, str):
context.response_status = 400
context.response_data = {"error": "refresh_token is required"}
return
try:
result = refresh_tokens(refresh_token_val)
except Exception:
context.response_status = 401
context.response_data = {"error": "Invalid or expired refresh token"}
return
context.response_status = 200
context.response_data = result
context.new_access_token = result["access_token"]
context.new_refresh_token = result["refresh_token"]
# ---------------------------------------------------------------------------
# Then
# ---------------------------------------------------------------------------
@then("the response status code should be {status_code:d}")
def step_status_code(context: Context, status_code: int) -> None:
"""Verify the response status code."""
assert context.response_status == status_code, (
f"Expected {status_code}, got {context.response_status}"
)
@then('the response should contain "{text}"')
def step_response_contains(context: Context, text: str) -> None:
"""Verify the response contains expected text."""
if isinstance(context.response_data, dict):
response_text = json.dumps(context.response_data)
else:
response_text = str(context.response_data)
assert text in response_text, f"Expected '{text}' in response: {response_text}"
@then("the new access token should be a valid JWT")
def step_verify_access_token_json(context: Context) -> None:
"""Verify the new access token is a valid JWT with correct claims."""
token = context.new_access_token
assert token is not None, "No access token in response"
payload = decode_token(token)
assert payload.get("type") == "access", (
f"Token type is '{payload.get('type')}', expected 'access'"
)
assert "sub" in payload, "Token missing 'sub' claim"
assert "iat" in payload, "Token missing 'iat' claim"
assert "exp" in payload, "Token missing 'exp' claim"
@then("the new refresh token should be a valid refresh token")
def step_verify_refresh_token(context: Context) -> None:
"""Verify the new refresh token is a valid refresh token."""
token = context.new_refresh_token
assert token is not None, "No refresh token in response"
payload = decode_token(token)
assert payload.get("type") == "refresh", (
f"Token type is '{payload.get('type')}', expected 'refresh'"
)
assert "sub" in payload, "Token missing 'sub' claim"
assert "iat" in payload, "Token missing 'iat' claim"
assert "exp" in payload, "Token missing 'exp' claim"