Skip to content

Conversation

@0xc7c5
Copy link
Collaborator

@0xc7c5 0xc7c5 commented Jan 12, 2026

Summary by CodeRabbit

  • New Features

    • Expanded auth surface: login, logout, refresh, profile, change username/password, audit-log; API client gains PATCH, custom requests, and cookie management.
  • Documentation

    • Added full OpenAPI 3.0 specification for the API.
  • Tests

    • Large end-to-end auth test suite, new fixtures and schema validators, token-fuzzing utilities, and edge-case constants for pagination, headers, validation, security, and race conditions.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 12, 2026

Caution

Review failed

The pull request is closed.

Walkthrough

Adds token-aware HTTP client features and a new AuthAPIClient; expands fixtures and token-fuzzing utilities; introduces Pydantic auth schemas and a large OpenAPI 3.0.3 contract; and adds extensive end-to-end authentication test suites (login, refresh, logout, profile, password, username, audit-log, authorization).

Changes

Cohort / File(s) Summary
API client & auth class
clients/api_client.py
APIClient gains refresh_token state, initializes self.headers, adds patch(), send_custom_request(), and cookie helpers (set_cookies(), get_cookies(), clear_cookies()). New AuthAPIClient(APIClient) exposing auth endpoints: login(), post_refresh(), logout(), get_profile(), change_password(), change_username(), get_audit_log().
Fixtures & test helpers
fixtures/api_fixtures.py
Adds faker (session) fixture; renames authenticated_clientauthenticated_nodes_client (session); adds auth_client, authenticated_auth_client, valid_credentials() helper, Faker-backed username/password fixtures, and invalid_credentials.
Fixture import path
fixtures/eth_fixtures.py
Updated import: from utils.eth_client import EthereumClientfrom clients.eth_client import EthereumClient.
OpenAPI contract
openapi.yaml
New OpenAPI 3.0.3 specification added (project root) describing CP Deployments API, paths (incl. auth), schemas and BearerAuth security.
Test constants & cases
tests/api/cases/const.py, tests/api/cases/test_cases.py
New MAX_64_BIT_INT constant and test-case collections: EMPTY_STRING_CASES, NONSTRING_CASES, NONINTEGER_CASES, WEAK_PASSWORD_CASES.
Pydantic test schema utilities
tests/api/schemas/__init__.py, tests/api/schemas/auth_schemas.py
Adds validate_schema(response_data, schema_class) test helper and Pydantic models for auth: UserProfile, LoginResponse, RefreshTokenResponse, LogoutResponse, ErrorResponse, ChangePasswordResponse, AuditLogEntry, AuditLogResponse.
Token fuzzing utilities
utils/token_generator.py
New functions generate_invalid_bearer_tokens() and generate_invalid_refresh_tokens() producing lists of malformed/invalid tokens for negative tests.
Authentication & authorization tests
tests/api/test_auth_*.py
tests/api/test_auth_login.py, tests/api/test_auth_refresh.py, tests/api/test_auth_logout.py, tests/api/test_auth_profile.py, tests/api/test_auth_password.py, tests/api/test_auth_username.py, tests/api/test_auth_audit_log.py, tests/api/test_auth_authorization.py
Large addition of end-to-end test suites covering login, refresh, logout, profile, password change, username change, audit-log, authorization: success paths, validation, access control, pagination, headers, concurrency, token revocation, schema validation and many edge cases.
Test schema helper package init
tests/api/schemas/__init__.py
Adds schema validation helper that fails tests on Pydantic ValidationError.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AuthAPI as AuthAPIClient
  participant Server
  participant DB
  Client->>AuthAPI: login(username, password)
  AuthAPI->>Server: POST /v1/auth/login (credentials)
  Server->>DB: validate credentials
  DB-->>Server: user record / auth decision
  Server-->>AuthAPI: 200 + access_token + refresh_token + user
  AuthAPI-->>Client: response (tokens, profile)
Loading
sequenceDiagram
  participant Client
  participant AuthAPI as AuthAPIClient
  participant Server
  participant TokenStore
  Client->>AuthAPI: post_refresh(refresh_token)
  AuthAPI->>Server: POST /v1/auth/refresh (refresh_token)
  Server->>TokenStore: verify/rotate refresh token
  TokenStore-->>Server: validation result + new tokens
  Server-->>AuthAPI: 200 + new access_token (+/- refresh_token)
  AuthAPI-->>Client: response (new tokens)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • alexgavrilov-lab

Poem

🐇 I hopped through headers, nudged tokens in the night,
I munched on schemas, fixtures shining bright,
Login, refresh, logout — a twitch, a tiny shove,
Audit crumbs and tests — I thumped my paws with love,
A rabbit's cheer for auth — swift, small, and light.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: comprehensive addition of authentication API tests and related infrastructure for the cp-autotests project.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/auth-tests-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@guardrails
Copy link

guardrails bot commented Jan 12, 2026

⚠️ We detected 1 security issue in this pull request:

Hard-Coded Secrets (1)
Severity Details Docs
Medium Title: JWT Detected
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.invalid",
📚

More info on how to fix Hard-Coded Secrets in Python.


👉 Go to the dashboard for detailed results.

📥 Happy? Share your feedback with us.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 23

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🤖 Fix all issues with AI agents
In @tests/api/test_auth_audit_log.py:
- Around line 199-217: Import the missing base64 module and ensure the modified
headers are applied to the client before calling the API: in the tests
test_get_audit_log_with_wrong_auth_format and
test_get_audit_log_with_too_long_access_token, add "import base64" if absent,
set authenticated_auth_client.headers = headers immediately after creating the
headers variable (before calling authenticated_auth_client.get_audit_log()),
then perform the assertions and ErrorResponse(**response.json()), and finally
restore the original headers if needed.
- Around line 189-197: Test test_get_audit_log_with_wrong_auth_type is missing
the base64 import and never applies the modified headers before calling
get_audit_log; add "import base64" at the top of the test module and update
test_get_audit_log_with_wrong_auth_type to assign the modified headers to
authenticated_auth_client.headers before invoking
authenticated_auth_client.get_audit_log() (or pass the headers into
get_audit_log if that method accepts them), and keep or restore the original
headers afterwards as needed.
- Around line 135-145: The test file defines test_audit_log_entry_structure
twice which causes the first definition to be shadowed and some assertions to
never run; rename one of the duplicates (e.g., to
test_audit_log_entry_structure_with_results) so both tests run, and fix the
inconsistent indentation in the first test body (the block under if
len(audit_log.results) > 0 should align with the if) — update the method name
and adjust indentation around test_audit_log_entry_structure and its use of
authenticated_auth_client and AuditLogResponse to restore correct structure and
execution.

In @tests/api/test_auth_login.py:
- Around line 157-163: The test test_login_missing_password is calling
auth_client.login with password=valid_credentials["password"] (a copy-paste
error) instead of omitting the password; update the call in
test_login_missing_password to call
auth_client.login(username=valid_credentials["username"]) so the request
contains only the username and validates the missing-password 400 response,
leaving the assertions and ErrorResponse parsing intact.
- Around line 237-246: In test_login_with_wrong_method, the test calls
send_custom_request on auth_client.client but send_custom_request is defined on
the API client itself; update the call to use auth_client.send_custom_request
(keep the same parameters and assertions) so the test invokes the correct method
(refer to test_login_with_wrong_method and send_custom_request).
- Around line 32-47: The test function duplicate named test_login_success causes
the second definition to shadow the first; rename this duplicate to a unique,
descriptive name (e.g., test_login_success_recurrent or
test_recurrent_login_with_valid_credentials) and update any references or
markers if needed; ensure the function signature using auth_client,
valid_credentials, authenticated_auth_client and assertions remain the same so
pytest runs both tests independently.
- Around line 294-305: The test uses ProfileResponse but that name isn't
imported/defined; replace its usage with the correct schema UserProfile and add
an import for UserProfile from the project schemas (or tests' schema module) so
the lines constructing the model become
UserProfile(**get_profile_response.json()); update both occurrences around
authenticated_auth_client.get_profile() and ensure the import is added to the
top of tests/api/test_auth_login.py alongside other test schema imports.

In @tests/api/test_auth_logout.py:
- Around line 217-226: The test test_logout_with_wrong_content_type incorrectly
reads/writes auth_client.headers (which doesn't exist); update it to call the
logout method (or underlying client request) with the explicit headers variable
instead of mutating auth_client.headers — e.g., build headers =
auth_client.headers.copy() should be replaced with a base headers dict from the
fixture or auth_client.get_default_headers(), then call
auth_client.logout(headers=headers) or auth_client.client.post("/logout",
headers=headers) and remove the final auth_client.headers assignment; ensure the
response assertion and ErrorResponse(**response.json()) remain unchanged.
- Around line 207-216: The test is incorrectly accessing a non-existent
auth_client.headers on APIClient; replace the header-manipulation and
auth_client.logout() call with a direct HTTP POST via auth_client.client to the
logout endpoint. Specifically, in test_logout_without_content_type use
auth_client.client.post to POST to f"{auth_client.base_url}/v1/auth/logout" with
content set to "{}" (or empty payload) and headers={} to omit Content-Type, then
assert response.status_code == 400 and validate the body with
ErrorResponse(**response.json()); remove any reads/writes to auth_client.headers
and the auth_client.logout() invocation.
- Around line 1-7: The tests in test_auth_logout.py call base64.b64encode() but
the module isn't imported; add "import base64" to the top of the file alongside
the other imports so the calls to base64.b64encode() in the logout tests execute
without NameError (no code change elsewhere; just add the base64 import).
- Around line 131-139: The test test_logout_with_too_long_access_token
constructs a modified headers dict but never uses it when calling
authenticated_auth_client.logout(), so the long Authorization token is not sent;
update the test to pass the modified headers into
authenticated_auth_client.logout(headers=...) (or set
authenticated_auth_client.headers = headers before the call) and then assert the
431 response, but account for the possibility of no JSON body by only calling
ErrorResponse(**response.json()) when response has a JSON content-type or
non-empty body.

In @tests/api/test_auth_password.py:
- Around line 229-241: The test test_change_password_extra_fields is calling
send_custom_request with the wrong kwarg name (using url=) which doesn't match
the client signature; update the call in test_change_password_extra_fields to
use endpoint= instead of url= when invoking
authenticated_auth_client.send_custom_request, keeping the same method and json
payload so the request is sent to the correct endpoint.
- Around line 1-6: The test file is missing imports for WEAK_PASSWORD_CASES,
generate_invalid_refresh_tokens, and the base64 module; add an import for base64
and import WEAK_PASSWORD_CASES and generate_invalid_refresh_tokens from the same
test_cases module where EMPTY_STRING_CASES and NONSTRING_CASES come from so
references to WEAK_PASSWORD_CASES, generate_invalid_refresh_tokens, and
base64.b64encode/b64decode resolve at runtime.

In @tests/api/test_auth_profile.py:
- Around line 1-6: The test file is missing the base64 import and references a
non-existent helper; add "import base64" at the top of
tests/api/test_auth_profile.py and replace usages of generate_invalid_tokens
with the correct helper(s) from utils/token_generator.py (e.g.,
generate_invalid_bearer_tokens or generate_invalid_refresh_tokens) or create a
thin wrapper named generate_invalid_tokens that calls the appropriate functions;
ensure any tests that expect both types call the right helper so there are no
NameError/ImportError at runtime.
- Around line 154-172: The tests test_get_profile_with_wrong_auth_type and
test_get_profile_with_wrong_auth_format fail to import base64 and also never
apply the modified headers when calling get_profile; add "import base64" to the
tests module and either set authenticated_auth_client.headers = headers before
calling authenticated_auth_client.get_profile() or pass headers into get_profile
(whichever matches the client API), then validate response and restore headers
if needed.

In @tests/api/test_auth_refresh.py:
- Around line 187-215: Tests modify an Authorization header but never apply it
to the client and they also use base64 without importing it; add "import base64"
at the module top and, in each test (test_refresh_with_wrong_auth_type,
test_refresh_with_wrong_auth_format, test_refresh_with_too_long_access_token),
assign the modified headers back to the client before making the request
(authenticated_auth_client.headers = headers) so the request uses the altered
Authorization value, then leave the existing restoration line after the request.
- Around line 1-6: The import and usage mismatch: replace the imported
generate_invalid_refresh_tokens with generate_invalid_bearer_tokens from
utils.token_generator, and update the call site that currently invokes
generate_invalid_tokens() (around the test using generate_invalid_tokens on line
163) to call generate_invalid_bearer_tokens() so the imported symbol matches the
function actually used; verify the test refers to generate_invalid_bearer_tokens
in tests/api/test_auth_refresh.py and remove or rename any incorrect references.

In @tests/api/test_auth_username.py:
- Around line 193-221: The tests test_change_username_with_wrong_auth_type /
test_change_username_with_wrong_auth_format /
test_change_username_with_too_long_access_token fail to import base64 and never
apply the modified headers to the client before making the request; add "import
base64" to the test module and, inside each test, assign the modified headers
back to authenticated_auth_client.headers before calling
authenticated_auth_client.change_username(...), then restore the original
headers afterward (or use a try/finally) so the request uses the altered
Authorization value.
- Around line 1-6: The test references two missing imports: add "import base64"
for the stdlib base64 usage and import the helper that produces bad tokens (e.g.
"from tests.api.utils import generate_invalid_tokens" or from whichever test
helper module houses that function in this repo) so generate_invalid_tokens and
base64 are available where EMPTY_STRING_CASES and NONSTRING_CASES are used in
tests/api/test_auth_username.py.
🟠 Major comments (4)
tests/api/test_auth_logout.py-228-235 (1)

228-235: Inconsistent Cache-Control expectations and authentication.

  1. This test uses auth_client (unauthenticated) but expects 200 - logout requires authentication per the OpenAPI spec.
  2. Cache-Control expectation (no-cache, no-store, must-revalidate) differs from test_logout_check_response_headers line 146 (no-store).

Clarify expected behavior and use consistent assertions.

clients/api_client.py-171-172 (1)

171-172: Method refresh_token shadows instance attribute of same name.

The APIClient.__init__ stores self.refresh_token (line 12), but this method has the same name. Accessing client.refresh_token returns the method, not the stored token value.

Proposed fix: rename method or attribute
-    def refresh_token(self, refresh_token: str) -> httpx.Response:
+    def refresh_access_token(self, refresh_token: str) -> httpx.Response:
         return self.post("/v1/auth/refresh", json={"refresh_token": refresh_token})

Or rename the attribute in APIClient.__init__:

-        self.refresh_token = refresh_token
+        self.stored_refresh_token = refresh_token
tests/api/test_auth_password.py-156-168 (1)

156-168: Wrong token generator function for access token testing.

Using generate_invalid_refresh_tokens() to test invalid access tokens is incorrect. These generators produce different token structures. Use generate_invalid_bearer_tokens() instead.

Proposed fix
     @allure.title("Change password with invalid authentication token")
     @allure.severity(allure.severity_level.CRITICAL)
-    @pytest.mark.parametrize("invalid_token", generate_invalid_refresh_tokens())
+    @pytest.mark.parametrize("invalid_token", generate_invalid_bearer_tokens())
     def test_change_password_with_invalid_token(self, authenticated_auth_client, invalid_token, valid_credentials, faker):
tests/api/test_auth_refresh.py-231-239 (1)

231-239: Test modifies headers but doesn't apply them before request.

headers.pop("Content-Type") modifies a copy, but the actual client headers remain unchanged when the request is made.

Proposed fix
     def test_refresh_without_content_type(self, authenticated_auth_client):
-        headers = authenticated_auth_client.headers.copy() 
-        headers.pop("Content-Type")
-        response = authenticated_auth_client.refresh_token(authenticated_auth_client.refresh_token)       
+        original_headers = authenticated_auth_client.headers.copy()
+        authenticated_auth_client.headers.pop("Content-Type", None)
+        response = authenticated_auth_client.refresh_token(authenticated_auth_client.refresh_token)
         assert response.status_code == 400, f"Expected 400, got {response.status_code}"
         ErrorResponse(**response.json())
-        authenticated_auth_client.headers = headers
+        authenticated_auth_client.headers = original_headers
🟡 Minor comments (7)
tests/api/cases/const.py-1-1 (1)

1-1: Clarify the constant's intent: max valid value vs. overflow value.

2**63 equals 9,223,372,036,854,775,808, which actually exceeds the maximum positive value of a signed 64-bit integer (2**63 - 1 = 9,223,372,036,854,775,807).

If this constant is meant to represent the maximum valid 64-bit signed integer for boundary testing, it should be:

MAX_64_BIT_INT = 2**63 - 1

If it's intentionally an overflow value for negative testing, consider renaming it to something like OVERFLOW_64_BIT_INT for clarity.

tests/api/cases/test_cases.py-2-3 (1)

2-3: Sets and tuples are not JSON-serializable.

Both NONSTRING_CASES and NONINTEGER_CASES include (1,2,3) (tuple) and {1,2,3} (set), which are not natively JSON-serializable. If these test cases are used directly in API request bodies, they will cause TypeError during JSON serialization.

Consider either:

  1. Removing sets and tuples if they're not needed for the test scenarios
  2. Converting them to lists [1,2,3] if the intent is to test array-like inputs
  3. Ensuring the test code handles serialization appropriately before sending
Suggested fix if lists are acceptable substitutes
-NONSTRING_CASES = [123, True, None, [123], {"key": "value"}, (1,2,3), {1,2,3}, 123.45]
-NONINTEGER_CASES = [True, None, [123], {"key": "value"}, (1,2,3), {1,2,3}, 123.45, "123", "null", "true", "false"]
+NONSTRING_CASES = [123, True, None, [123], {"key": "value"}, [1,2,3], 123.45]
+NONINTEGER_CASES = [True, None, [123], {"key": "value"}, [1,2,3], 123.45, "123", "null", "true", "false"]
tests/api/test_auth_logout.py-85-88 (1)

85-88: Typo in class name: TestLogoutAcessTestLogoutAccess.

Proposed fix
 @allure.feature("Authentication")
 @allure.story("Logout")
 @pytest.mark.api
-class TestLogoutAcess:
+class TestLogoutAccess:
tests/api/test_auth_authorization.py-158-172 (1)

158-172: Incomplete test: only /healthz is verified.

The test defines three public endpoints but the loop only executes logic for /healthz. The login and refresh endpoints aren't tested for accessibility.

Proposed fix to test all endpoints
     def test_public_endpoints_no_auth(self, auth_client):
         public_endpoints = [
             "/healthz",
             "/v1/auth/login",
             "/v1/auth/refresh",
         ]
         
         for endpoint in public_endpoints:
             with allure.step(f"Testing public endpoint: {endpoint}"):
                 if endpoint == "/healthz":
                     response = auth_client.get(endpoint)
                     assert response.status_code in [200, 404], \
                         f"{endpoint} should be accessible without auth"
+                elif endpoint == "/v1/auth/login":
+                    response = auth_client.post(endpoint, json={"username": "", "password": ""})
+                    # Login fails but endpoint is accessible (not 401 for missing auth)
+                    assert response.status_code in [400, 401], \
+                        f"{endpoint} should be accessible without auth"
+                elif endpoint == "/v1/auth/refresh":
+                    response = auth_client.post(endpoint, json={"refresh_token": ""})
+                    assert response.status_code in [400, 401], \
+                        f"{endpoint} should be accessible without auth"
fixtures/api_fixtures.py-38-49 (1)

38-49: Silent failure when login fails in authenticated_auth_client.

If login returns non-200, the fixture silently yields a client without valid tokens. Tests will fail with confusing 401 errors rather than a clear setup failure.

Proposed fix to raise on login failure
 @pytest.fixture
 def authenticated_auth_client(config: Settings):
     client = AuthAPIClient(config)
     if config.user_log and config.user_pass:
         response = client.login(config.user_log, config.user_pass)
         if response.status_code == 200:
             token = response.json().get("access_token")
             refresh_token = response.json().get("refresh_token")
             client.token = token
             client.refresh_token = refresh_token
+        else:
+            raise RuntimeError(f"Login failed with status {response.status_code}: {response.text}")
+    else:
+        raise RuntimeError("Missing USER_LOG or USER_PASS in configuration")
     yield client
     client.close()
clients/api_client.py-168-169 (1)

168-169: Invalid type annotation: any should be Any or Optional[str].

Python's any is a built-in function, not a type. Use Any from typing or preferably Optional[str] for explicit typing.

Proposed fix
-    def login(self, username: any = None, password: any = None) -> httpx.Response:        
+    def login(self, username: Optional[str] = None, password: Optional[str] = None) -> httpx.Response:        
         return self.post("/v1/auth/login", json={"username": username, "password": password})
tests/api/test_auth_profile.py-100-111 (1)

100-111: Unused valid_roles variable - role validation is incomplete.

The valid_roles list is defined but never used. It appears the intent was to validate that tenant_role is one of the allowed values.

Proposed fix
     def test_profile_tenant_role(self, authenticated_auth_client):
         response = authenticated_auth_client.get_profile()
         
         assert response.status_code == 200
         
         profile = UserProfile(**response.json())
         valid_roles = ["owner", "admin", "editor", "viewer"]
         
         assert profile.tenant_role, "Tenant role should not be empty"
+        assert profile.tenant_role in valid_roles, f"Tenant role '{profile.tenant_role}' should be one of {valid_roles}"
🧹 Nitpick comments (7)
utils/token_generator.py (1)

9-84: Good coverage of invalid token scenarios.

The function provides comprehensive test cases covering malformed structure, encoding issues, tampered signatures, and injection attempts.

Nit: Lines 51, 52, and 64 have f-string prefixes without any placeholders. While harmless, they can be simplified.

Remove unnecessary f-string prefixes
         # Malformed structure
-        f"...",
-        f"....",
+        "...",
+        "....",
         f"{valid_header}...{valid_signature}",
         
         # Invalid JSON in parts
         "bm90X2pzb24.bm90X2pzb24.signature",  # "not_json" base64 encoded
         
         # Tampered signature
         f"{valid_header}.{valid_payload}.tampered_signature",
         f"{valid_header}.{valid_payload}.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
         
         # Special characters
         f"{valid_header}.{valid_payload}.sig@#$%",
-        f"header!@#.payload$%^.signature&*()",
+        "header!@#.payload$%^.signature&*()",
tests/api/schemas/__init__.py (1)

5-22: LGTM!

Clean utility function that centralizes schema validation logic for tests. The error message provides good context by including the schema class name and validation error details.

Optional: Consider adding a type hint for schema_class for better IDE support:

from typing import Type, TypeVar
from pydantic import BaseModel

T = TypeVar('T', bound=BaseModel)

def validate_schema(response_data: dict, schema_class: Type[T]) -> T:
openapi.yaml (3)

1-12: Consider adding global security with explicit overrides for public endpoints.

The spec defines BearerAuth but doesn't set it globally. While login/refresh correctly don't require auth, the deployment and preset endpoints appear unprotected. If these should require authentication, apply security globally and override with security: [] for public endpoints.

# Add after servers section
security:
  - BearerAuth: []

Then for public endpoints like /v1/auth/login:

security: []  # Public endpoint

1157-1177: Consider adding maxItems constraints to array schemas.

List endpoints like AuditLogResponse.results lack maxItems bounds. While page_size has a maximum of 100, explicitly constraining the array provides defense-in-depth and clearer API contracts.

results:
  type: array
  maxItems: 100
  items:
    $ref: '#/components/schemas/AuditLogEntry'

1186-1196: Unused "Internal" tag defined.

The Internal tag (line 1193-1194) is defined but no endpoints in the paths section use it. Consider removing it or documenting its intended future use.

clients/api_client.py (1)

187-189: Audit log params only sent when both page and page_size are provided.

The condition if page and page_size requires both values. Users expecting to set only page_size will get no params. Consider sending non-None values independently.

Proposed fix
     def get_audit_log(self, page: Optional[int] = None, page_size: Optional[int] = None) -> httpx.Response:
-        params = {"page": page, "page_size": page_size} if page and page_size else None
+        params = {}
+        if page is not None:
+            params["page"] = page
+        if page_size is not None:
+            params["page_size"] = page_size
         return self.get("/v1/auth/audit-log", params=params)
tests/api/test_auth_profile.py (1)

193-223: TestProfileHeaders class is missing allure feature/story decorators.

Unlike other test classes in this file, TestProfileHeaders lacks the @allure.feature and @allure.story decorators, which will cause inconsistent test reporting.

Proposed fix
+@allure.feature("Authentication")
+@allure.story("User Profile")
+@pytest.mark.api
 class TestProfileHeaders:
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5eb0266 and 54e01cf.

📒 Files selected for processing (20)
  • clients/api_client.py
  • clients/eth_client.py
  • fixtures/api_fixtures.py
  • fixtures/eth_fixtures.py
  • openapi.yaml
  • tests/api/README.md
  • tests/api/__init__.py
  • tests/api/cases/const.py
  • tests/api/cases/test_cases.py
  • tests/api/schemas/__init__.py
  • tests/api/schemas/auth_schemas.py
  • tests/api/test_auth_audit_log.py
  • tests/api/test_auth_authorization.py
  • tests/api/test_auth_login.py
  • tests/api/test_auth_logout.py
  • tests/api/test_auth_password.py
  • tests/api/test_auth_profile.py
  • tests/api/test_auth_refresh.py
  • tests/api/test_auth_username.py
  • utils/token_generator.py
🧰 Additional context used
🧬 Code graph analysis (9)
tests/api/test_auth_logout.py (5)
tests/api/schemas/auth_schemas.py (3)
  • LogoutResponse (29-30)
  • ErrorResponse (33-34)
  • UserProfile (6-14)
utils/token_generator.py (1)
  • generate_invalid_refresh_tokens (86-167)
fixtures/api_fixtures.py (2)
  • authenticated_auth_client (39-49)
  • auth_client (32-35)
clients/api_client.py (4)
  • logout (174-176)
  • refresh_token (171-172)
  • get_profile (178-179)
  • send_custom_request (85-99)
fixtures/playwright_fixtures.py (1)
  • base_url (38-39)
utils/token_generator.py (1)
fixtures/api_fixtures.py (1)
  • faker (7-8)
tests/api/test_auth_authorization.py (3)
utils/token_generator.py (1)
  • generate_invalid_bearer_tokens (9-84)
fixtures/api_fixtures.py (3)
  • auth_client (32-35)
  • valid_credentials (53-57)
  • invalid_credentials (79-83)
clients/api_client.py (5)
  • send_custom_request (85-99)
  • get_profile (178-179)
  • get (31-42)
  • refresh_token (171-172)
  • logout (174-176)
tests/api/schemas/auth_schemas.py (1)
clients/api_client.py (1)
  • refresh_token (171-172)
clients/api_client.py (1)
config/settings.py (1)
  • Settings (6-78)
fixtures/eth_fixtures.py (1)
clients/eth_client.py (1)
  • EthereumClient (8-153)
tests/api/test_auth_refresh.py (4)
tests/api/schemas/auth_schemas.py (4)
  • LoginResponse (17-21)
  • RefreshTokenResponse (24-26)
  • ErrorResponse (33-34)
  • UserProfile (6-14)
utils/token_generator.py (1)
  • generate_invalid_refresh_tokens (86-167)
fixtures/api_fixtures.py (3)
  • auth_client (32-35)
  • valid_credentials (53-57)
  • authenticated_auth_client (39-49)
clients/api_client.py (4)
  • refresh_token (171-172)
  • post (45-56)
  • get_profile (178-179)
  • logout (174-176)
tests/api/test_auth_profile.py (3)
tests/api/schemas/auth_schemas.py (3)
  • UserProfile (6-14)
  • ErrorResponse (33-34)
  • LoginResponse (17-21)
fixtures/api_fixtures.py (2)
  • authenticated_auth_client (39-49)
  • valid_credentials (53-57)
clients/api_client.py (3)
  • get_profile (178-179)
  • login (168-169)
  • logout (174-176)
tests/api/test_auth_username.py (3)
tests/api/schemas/auth_schemas.py (2)
  • UserProfile (6-14)
  • ErrorResponse (33-34)
fixtures/api_fixtures.py (3)
  • authenticated_auth_client (39-49)
  • valid_username (65-66)
  • valid_credentials (53-57)
clients/api_client.py (3)
  • change_username (184-185)
  • send_custom_request (85-99)
  • logout (174-176)
🪛 Checkov (3.2.334)
openapi.yaml

[high] 1-1198: Ensure that the global security field has rules defined

(CKV_OPENAPI_4)


[high] 1-1198: Ensure that security operations is not empty.

(CKV_OPENAPI_5)


[medium] 48-52: Ensure that arrays have a maximum number of items

(CKV_OPENAPI_21)

🪛 Gitleaks (8.30.0)
tests/api/test_auth_username.py

[high] 184-184: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

🪛 markdownlint-cli2 (0.18.1)
tests/api/README.md

18-18: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


30-30: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


42-42: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


56-56: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


72-72: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


82-82: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🪛 Ruff (0.14.10)
tests/api/test_auth_logout.py

115-115: Undefined name base64

(F821)


125-125: Undefined name base64

(F821)

utils/token_generator.py

26-26: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


51-51: f-string without any placeholders

Remove extraneous f prefix

(F541)


52-52: f-string without any placeholders

Remove extraneous f prefix

(F541)


64-64: f-string without any placeholders

Remove extraneous f prefix

(F541)


108-108: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)

tests/api/test_auth_authorization.py

46-46: Possible hardcoded password assigned to: "expired_token"

(S105)

clients/api_client.py

99-99: Avoid specifying long messages outside the exception class

(TRY003)


168-168: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)


168-168: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)

tests/api/test_auth_password.py

106-106: Undefined name WEAK_PASSWORD_CASES

(F821)


158-158: Undefined name generate_invalid_refresh_tokens

(F821)


174-174: Undefined name base64

(F821)


187-187: Undefined name base64

(F821)

tests/api/test_auth_login.py

35-35: Redefinition of unused test_login_success from line 16

(F811)


93-93: Undefined name valid_credentials

(F821)


93-93: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


93-93: Undefined name valid_credentials

(F821)


94-94: Undefined name valid_credentials

(F821)


94-94: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


94-94: Undefined name valid_credentials

(F821)


95-95: Undefined name valid_credentials

(F821)


95-95: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


95-95: Undefined name valid_credentials

(F821)


96-96: Undefined name valid_credentials

(F821)


96-96: Undefined name valid_credentials

(F821)


96-96: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


97-97: Undefined name valid_credentials

(F821)


97-97: Undefined name valid_credentials

(F821)


97-97: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


98-98: Undefined name valid_credentials

(F821)


98-98: Undefined name valid_credentials

(F821)


98-98: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


99-99: Undefined name valid_credentials

(F821)


99-99: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


99-99: Undefined name valid_credentials

(F821)


99-99: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


100-100: Undefined name valid_credentials

(F821)


100-100: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


100-100: Undefined name valid_credentials

(F821)


100-100: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


101-101: Undefined name valid_credentials

(F821)


101-101: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


101-101: Undefined name valid_credentials

(F821)


101-101: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


112-112: Undefined name valid_credentials

(F821)


112-112: Undefined name valid_credentials

(F821)


113-113: Undefined name valid_credentials

(F821)


113-113: Undefined name valid_credentials

(F821)


114-114: Undefined name valid_credentials

(F821)


114-114: Undefined name valid_credentials

(F821)


115-115: Undefined name valid_credentials

(F821)


115-115: Undefined name valid_credentials

(F821)


116-116: Undefined name valid_credentials

(F821)


116-116: Undefined name valid_credentials

(F821)


117-117: Undefined name valid_credentials

(F821)


117-117: Undefined name valid_credentials

(F821)


118-118: Undefined name valid_credentials

(F821)


118-118: Undefined name valid_credentials

(F821)


119-119: Undefined name valid_credentials

(F821)


119-119: Undefined name valid_credentials

(F821)


120-120: Undefined name valid_credentials

(F821)


120-120: Undefined name valid_credentials

(F821)


121-121: Undefined name valid_credentials

(F821)


121-121: Undefined name valid_credentials

(F821)


122-122: Undefined name valid_credentials

(F821)


122-122: Undefined name valid_credentials

(F821)


298-298: Undefined name ProfileResponse

(F821)


304-304: Undefined name ProfileResponse

(F821)

tests/api/test_auth_refresh.py

71-71: Possible hardcoded password assigned to: "expired_token"

(S105)


163-163: Undefined name generate_invalid_tokens

(F821)


178-178: Possible hardcoded password assigned to: "expired_token"

(S105)


191-191: Undefined name base64

(F821)


201-201: Undefined name base64

(F821)

tests/api/test_auth_profile.py

108-108: Local variable valid_roles is assigned to but never used

Remove assignment to unused variable valid_roles

(F841)


145-145: Possible hardcoded password assigned to: "expired_token"

(S105)


158-158: Undefined name base64

(F821)


168-168: Undefined name base64

(F821)

tests/api/test_auth_audit_log.py

149-149: Redefinition of unused test_audit_log_entry_structure from line 135

(F811)


193-193: Undefined name base64

(F821)


203-203: Undefined name base64

(F821)

tests/api/test_auth_username.py

169-169: Undefined name generate_invalid_tokens

(F821)


184-184: Possible hardcoded password assigned to: "expired_token"

(S105)


197-197: Undefined name base64

(F821)


207-207: Undefined name base64

(F821)

🔇 Additional comments (9)
fixtures/eth_fixtures.py (1)

2-2: LGTM!

The import path update aligns with the broader repository refactoring to centralize client modules under the clients package. The EthereumClient class exists at the new location as confirmed by the relevant code snippets.

utils/token_generator.py (1)

86-167: LGTM!

Good implementation including the wrong_type_token case to test token type confusion attacks (using an access token where a refresh token is expected). The test cases mirror the bearer token generator appropriately while including refresh-specific scenarios.

tests/api/README.md (1)

1-202: LGTM! Comprehensive test documentation.

The README provides clear guidance on test coverage, running instructions, configuration, and CI/CD integration. The structure aligns well with the test suite being introduced.

fixtures/api_fixtures.py (1)

6-83: LGTM! Well-structured test fixtures.

The fixture hierarchy is clean with appropriate scoping. The faker-based fixtures provide good test data isolation.

tests/api/test_auth_logout.py (1)

12-75: LGTM! Core logout tests are well-structured.

The test coverage for logout success, token invalidation, and refresh token revocation is comprehensive with proper schema validation.

tests/api/test_auth_authorization.py (1)

1-55: LGTM! Comprehensive authorization test coverage.

The test suite covers critical auth scenarios: protected endpoints, invalid tokens, expiration, token isolation, and scheme validation. The TODO comments appropriately flag areas needing clarification.

tests/api/schemas/auth_schemas.py (1)

1-55: LGTM! Clean Pydantic schema definitions.

The models correctly mirror the OpenAPI spec with appropriate field constraints (expires_in > 0) and optional fields. The nested UserProfile in LoginResponse enables comprehensive response validation.

clients/api_client.py (1)

159-180: LGTM! AuthAPIClient provides clean abstraction for auth endpoints.

The client methods align well with the OpenAPI spec. Cookie management methods add useful functionality for session-based testing.

tests/api/test_auth_username.py (1)

96-109: Verify: Should common username patterns like user_name and user.name be rejected?

The test expects HTTP 400 for usernames containing dots, underscores, hyphens, and digits. These are typically valid username characters in most systems. Please verify if this aligns with your API's username requirements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @clients/api_client.py:
- Around line 84-99: send_custom_request calls self.patch but APIClient lacks a
patch method, causing an AttributeError; add a patch method to APIClient
(similar to existing get/post/put) that accepts endpoint, optional json and
headers, builds url from self.base_url and request headers via
self._get_headers, makes the request via self.client.patch(url, json=json,
headers=request_headers), and wraps request/response logging with the same
allure.step/attachments pattern used by get/post/put so PATCH requests via
send_custom_request succeed.
- Around line 187-188: The current falsy check in get_audit_log uses "if page
and page_size" which treats page=0 as False and omits valid pagination; change
the condition to explicitly check for None (e.g., use "if page is not None and
page_size is not None") or build params by including keys when their values are
not None (e.g., params = {k: v for k, v in (("page", page), ("page_size",
page_size)) if v is not None}) so page=0 is preserved.
- Around line 168-169: The login method uses the builtin function name "any" as
a type hint and includes None values in the request body; update the signature
of login (function name: login) to use proper types (e.g., Optional[str] or str
| None) for username and password, and build the JSON payload so that keys with
None values are omitted before calling self.post (i.e., only include "username"
and "password" in the JSON when they are not None).
🧹 Nitpick comments (1)
clients/api_client.py (1)

171-172: Method name refresh_token shadows the instance attribute.

The method refresh_token shadows self.refresh_token set in __init__. This can cause confusion and makes the instance attribute inaccessible via self.refresh_token after class definition.

♻️ Proposed rename
-    def refresh_token(self, refresh_token: str) -> httpx.Response:
+    def refresh_access_token(self, refresh_token: str) -> httpx.Response:
         return self.post("/v1/auth/refresh", json={"refresh_token": refresh_token})
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 54e01cf and 6547b3f.

📒 Files selected for processing (1)
  • clients/api_client.py
🧰 Additional context used
🧬 Code graph analysis (1)
clients/api_client.py (1)
config/settings.py (1)
  • Settings (6-78)
🪛 Ruff (0.14.10)
clients/api_client.py

99-99: Avoid specifying long messages outside the exception class

(TRY003)


168-168: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)


168-168: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: guardrails/scan
🔇 Additional comments (2)
clients/api_client.py (2)

9-14: LGTM!

The addition of refresh_token parameter to the base APIClient follows the existing pattern and properly supports the new AuthAPIClient subclass.


101-109: LGTM!

Cookie management methods are cleanly implemented using the underlying httpx client's cookie jar.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🤖 Fix all issues with AI agents
In @clients/api_client.py:
- Around line 94-96: send_custom_request delegates PATCH to self.patch but
APIClient has no patch method, causing AttributeError; add a patch method on
APIClient (mirror the existing post/put methods) that accepts endpoint, json,
params, headers (or same signature as put/post) and forwards to the shared
request implementation (e.g., call the internal _request or session.request with
method "PATCH") so send_custom_request can call self.patch without error.

In @tests/api/test_auth_audit_log.py:
- Around line 190-198: The test copies and mutates a headers dict but never
assigns it back to the client, so the modified Authorization is not sent; update
the test_get_audit_log_with_wrong_auth_type test to set
authenticated_auth_client.headers = headers before calling
authenticated_auth_client.get_audit_log(), and apply the same change to the
other affected tests (lines referenced 200-208 and 210-218) so the mutated
headers are actually used for the request.
- Around line 182-185: The test calls
auth_client.get_audit_log(token=invalid_token) but get_audit_log(page=None,
page_size=None) doesn't accept a token and will raise TypeError; fix by removing
the token kwarg and setting the invalid token on the client before invoking
get_audit_log (e.g., use the client's token setter or assign auth_client.token /
call auth_client.set_token(invalid_token) depending on the client API), then
call auth_client.get_audit_log() and assert 401 as before; update
test_get_audit_log_with_invalid_access_token accordingly.

In @tests/api/test_auth_login.py:
- Around line 202-210: In test_login_without_content_type update the modified
headers dict back onto the client before making the request: after popping
"Content-Type" assign headers to auth_client.headers so auth_client.login uses
the altered headers, and restore the original headers after the assertion; apply
the same fix to the other test referenced (the block covering 212-221) so both
tests actually send the modified headers when calling auth_client.login.
- Around line 107-114: The parametrize call uses
randomize_valid_credentials(valid_credentials) at import time but
valid_credentials is a fixture and not available yet; update
test_login_wrong_case to obtain valid_credentials via the fixture and call
randomize_valid_credentials inside the test (or change to a fixture with params)
instead of in the @pytest.mark.parametrize decorator so that randomization
happens at runtime; specifically modify the usage around test_login_wrong_case
and randomize_valid_credentials (and/or convert valid_credentials to a
parametrized fixture) rather than referencing valid_credentials at module load.
- Around line 88-105: The parametrize currently references the fixture
valid_credentials at collection time and uses nested f-string quoting; instead
parametrize only the whitespace variants (e.g. "prefix", "suffix", "both") or
simple templates like (" {u}", "{u} ", " {u} ") for username and similarly for
password, and then inside test_login_stripcases accept the valid_credentials
fixture and build username/password by formatting those templates with
valid_credentials["username"]/["password"] (avoid nested quotes by using
.format() or concatenation). Update the pytest.mark.parametrize decorator to
pass plain templates/flags (no fixture access) and adjust
test_login_stripcases(auth_client, valid_credentials, username_template,
password_template) to construct the final values before calling
auth_client.login.

In @tests/api/test_auth_logout.py:
- Around line 68-75: Summary: The test method
test_refresh_token_invalidated_after_logout uses
authenticated_auth_client.refresh_token both as the stored token attribute and
as a method, causing a str-to-call TypeError; rename the attribute and update
calls. Rename the stored token attribute on the test client (e.g., refresh_token
-> stored_refresh_token or saved_refresh_token) and change the test to pass
authenticated_auth_client.stored_refresh_token into the client methods (e.g.,
authenticated_auth_client.logout(authenticated_auth_client.stored_refresh_token)
and
authenticated_auth_client.refresh_token(authenticated_auth_client.stored_refresh_token));
update any other usages of the old attribute name in tests/helpers to the new
name to avoid shadowing the refresh_token method.
- Around line 164-172: In test_logout_and_refresh_token_at_the_same_time, avoid
shadowing the client's refresh_token attribute by first storing it in a local
variable (e.g., token = authenticated_auth_client.refresh_token) and use that
variable for both calls; then change the sequential calls to run concurrently
(use concurrent.futures.ThreadPoolExecutor to submit
authenticated_auth_client.logout(refresh_token=token) and
authenticated_auth_client.refresh_token(refresh_token=token) in parallel),
collect the two futures, then assert logout_response.status_code == 200 and
refresh_response.status_code == 401 and validate payloads with
LogoutResponse(...) and ErrorResponse(...).

In @tests/api/test_auth_password.py:
- Around line 196-207: The test modifies a local headers copy but never assigns
it back to the client, so the request still uses the original headers; update
the test_change_password_with_too_long_access_token to set
authenticated_auth_client.headers = headers before calling change_password and
then restore the original headers afterwards (save original =
authenticated_auth_client.headers.copy(), assign modified headers, run
change_password, assert and validate ErrorResponse, then restore
authenticated_auth_client.headers = original); apply the same pattern to the
other similar tests referenced (lines 243-254 and 256-268) that modify a headers
copy instead of the client’s headers.
- Around line 1-6: The test file imports are missing WEAK_PASSWORD_CASES,
generate_invalid_refresh_tokens, and base64 which causes NameError/undefined
symbol failures; add imports at the top of the file: import base64, import
WEAK_PASSWORD_CASES from its test data module (same place as
EMPTY_STRING_CASES/NONSTRING_CASES), and import generate_invalid_refresh_tokens
from the helper/case utilities where other generator helpers live so the symbols
WEAK_PASSWORD_CASES, generate_invalid_refresh_tokens, and base64 are defined
before they are used in the tests.

In @tests/api/test_auth_profile.py:
- Around line 196-204: The tests modify a local headers copy but never apply it
to the client, so the request still uses the original headers; update both
test_get_profile_without_content_type and
test_get_profile_with_wrong_content_type to apply the modified headers before
making the request by assigning authenticated_auth_client.headers = headers (or
passing headers into authenticated_auth_client.get_profile(...) if that helper
accepts a headers param) so the request actually uses the removed/incorrect
Content-Type and then assert the 400 response and validate ErrorResponse as
before.
- Around line 155-163: The modified headers aren’t used because you build a new
headers dict but call authenticated_auth_client.get_profile() before applying
it; update the test_get_profile_with_wrong_auth_type to assign the modified
headers to authenticated_auth_client.headers (or pass headers into get_profile
if that method accepts it) before calling get_profile so the request uses the
"Basic ..." Authorization value, then perform the assertions and restore any
original headers if needed.

In @tests/api/test_auth_refresh.py:
- Around line 231-239: The test creates a copy of headers but pops
"Content-Type" only from the copy and then calls
authenticated_auth_client.refresh_token which still uses the original client
headers; update test_refresh_without_content_type to either assign the modified
headers back to authenticated_auth_client.headers before calling refresh_token
(and restore the original after) or call refresh_token with the modified headers
if the method accepts headers; apply the same change to the similar test block
at lines 241-250 so the request actually omits Content-Type.
- Around line 1-6: The test file is missing the base64 module import used by
base64.b64encode in the refresh token tests; add "import base64" to the top
import block alongside pytest/allure/time so calls to base64.b64encode succeed;
update the imports in tests/api/test_auth_refresh.py (near the existing imports
for pytest, allure, time, and pydantic.ValidationError) to include base64.
🟠 Major comments (7)
clients/api_client.py-172-173 (1)

172-173: Method refresh_token shadows instance attribute self.refresh_token.

This causes issues in tests where authenticated_auth_client.refresh_token is used both as an attribute (the stored token) and as a method call. Consider renaming the method to avoid confusion.

Proposed fix - rename method
-    def refresh_token(self, refresh_token: str) -> httpx.Response:
+    def refresh_access_token(self, refresh_token: str) -> httpx.Response:
         return self.post("/v1/auth/refresh", json={"refresh_token": refresh_token})

This requires updating all call sites in tests accordingly.

tests/api/test_auth_login.py-143-149 (1)

143-149: Test name and implementation mismatch.

Test is titled "Login fails with missing password" but line 146 passes password=valid_credentials["username"] instead of username=valid_credentials["username"]. This tests a scenario with missing username, not missing password.

Proposed fix
     @allure.title("Login fails with missing password")
     @allure.severity(allure.severity_level.NORMAL)
     def test_login_missing_password(self, auth_client, valid_credentials):
-        response = auth_client.login(password=valid_credentials["username"])
+        response = auth_client.login(username=valid_credentials["username"])
         
         assert response.status_code == 400, f"Expected 400, got {response.status_code}"
clients/api_client.py-188-189 (1)

188-189: Params only sent when both page and page_size are provided.

The condition if page and page_size means single-parameter requests (page-only or page_size-only) won't include any query params. Tests like test_get_audit_log_page_only expect this to work.

Proposed fix
     def get_audit_log(self, page: Optional[int] = None, page_size: Optional[int] = None) -> httpx.Response:
-        params = {"page": page, "page_size": page_size} if page and page_size else None
+        params = {}
+        if page is not None:
+            params["page"] = page
+        if page_size is not None:
+            params["page_size"] = page_size
         return self.get("/v1/auth/audit-log", params=params if params else None)
clients/api_client.py-169-170 (1)

169-170: Use Any from typing instead of lowercase any.

any is Python's built-in function, not a type hint. This should be Any from the typing module, or more appropriately Optional[str] since credentials are strings.

Proposed fix
-    def login(self, username: any = None, password: any = None) -> httpx.Response:        
+    def login(self, username: Optional[str] = None, password: Optional[str] = None) -> httpx.Response:        
         return self.post("/v1/auth/login", json={"username": username, "password": password})
tests/api/test_auth_profile.py-185-192 (1)

185-192: login() called without credentials will fail — pass username and password explicitly.

On line 192, authenticated_auth_client.login() is called without arguments, which sends {"username": null, "password": null} to the authentication API. The AuthAPIClient does not cache credentials internally, so there is no mechanism to retrieve stored credentials for re-authentication. This will result in an authentication failure.

Pass the configured credentials explicitly:

authenticated_auth_client.login(config.user_log, config.user_pass)

Or store credentials in the client during initialization for reuse.

tests/api/test_auth_username.py-195-203 (1)

195-203: Bug: Modified headers not used in request, then restoration overwrites valid headers.

Line 198 copies headers, line 199 sets Authorization on the copy but never applies it to the client. The request on line 200 uses the original (unchanged) client.headers. Then line 203 restores headers (with the wrong auth type) as the client headers, corrupting state for subsequent tests.

If the intent is to test with Basic auth, you need to modify authenticated_auth_client.headers directly before the request and restore afterward.

Proposed fix
     def test_change_username_with_wrong_auth_type(self, authenticated_auth_client, valid_username):
-        headers = authenticated_auth_client.headers.copy() 
-        headers["Authorization"] = "Basic " + base64.b64encode(authenticated_auth_client.token.encode()).decode() 
-        response = authenticated_auth_client.change_username(valid_username)       
+        original_headers = authenticated_auth_client.headers.copy()
+        authenticated_auth_client.headers["Authorization"] = "Basic " + base64.b64encode(authenticated_auth_client.token.encode()).decode()
+        response = authenticated_auth_client.change_username(valid_username)
         assert response.status_code == 401, f"Expected 401, got {response.status_code}"
         ErrorResponse(**response.json())
-        authenticated_auth_client.headers = headers
+        authenticated_auth_client.headers = original_headers
tests/api/test_auth_username.py-239-258 (1)

239-258: Bug: Header modifications not applied to client before request.

Both tests copy headers, modify the copy, but never apply the changes to the client before making the request:

  • Line 243: headers.pop("Content-Type") modifies the copy, not authenticated_auth_client.headers
  • Line 254: headers["Content-Type"] = content_type modifies the copy, not the client

The requests on lines 244 and 255 use the original unmodified client headers.

Proposed fix for test_change_username_without_content_type
     def test_change_username_without_content_type(self, authenticated_auth_client, valid_username):
-        headers = authenticated_auth_client.headers.copy() 
-        headers.pop("Content-Type")
-        response = authenticated_auth_client.change_username(valid_username)  
+        original_headers = authenticated_auth_client.headers.copy()
+        authenticated_auth_client.headers.pop("Content-Type", None)
+        response = authenticated_auth_client.change_username(valid_username)
         assert response.status_code == 400, f"Expected 400, got {response.status_code}"
         ErrorResponse(**response.json())
-        authenticated_auth_client.headers = headers
+        authenticated_auth_client.headers = original_headers
Proposed fix for test_change_username_with_wrong_content_type
     def test_change_username_with_wrong_content_type(self, authenticated_auth_client, content_type, valid_username):
-        headers = authenticated_auth_client.headers.copy() 
-        headers["Content-Type"] = content_type
-        response = authenticated_auth_client.change_username(valid_username)       
+        original_headers = authenticated_auth_client.headers.copy()
+        authenticated_auth_client.headers["Content-Type"] = content_type
+        response = authenticated_auth_client.change_username(valid_username)
         assert response.status_code == 400, f"Expected 400, got {response.status_code}"
         ErrorResponse(**response.json())
-        authenticated_auth_client.headers = headers
+        authenticated_auth_client.headers = original_headers
🟡 Minor comments (4)
tests/api/test_auth_logout.py-86-89 (1)

86-89: Typo in class name: TestLogoutAcess should be TestLogoutAccess.

Proposed fix
-class TestLogoutAcess:
+class TestLogoutAccess:
tests/api/test_auth_profile.py-109-111 (1)

109-111: Unused variable valid_roles.

The valid_roles list is defined but never used. The assertion only checks if tenant_role is truthy, not whether it's one of the valid roles.

Proposed fix to validate against allowed roles
         profile = UserProfile(**response.json())
         valid_roles = ["owner", "admin", "editor", "viewer"]
         
-        assert profile.tenant_role, "Tenant role should not be empty"
+        assert profile.tenant_role in valid_roles, f"Tenant role should be one of {valid_roles}, got {profile.tenant_role}"
tests/api/test_auth_username.py-34-40 (1)

34-40: Test does not match its description: sends null instead of omitting field.

The test is named test_change_username_missing_field, but passing None to change_username() sends {"new_username": null} (per api_client.py:184-185), not a request with the field missing. To test a truly missing field, use send_custom_request with an empty JSON body.

Proposed fix
     @allure.title("Change username with missing new_username")
     @allure.severity(allure.severity_level.NORMAL)
     def test_change_username_missing_field(self, authenticated_auth_client):
-        response = authenticated_auth_client.change_username(None)
+        response = authenticated_auth_client.send_custom_request(
+            "PUT",
+            "/v1/auth/username",
+            json={}
+        )
         
         assert response.status_code == 400, f"Expected 400, got {response.status_code}"
         ErrorResponse(**response.json())
tests/api/test_auth_username.py-225-232 (1)

225-232: Remove the login() call or pass credentials explicitly.

Line 232 calls authenticated_auth_client.login() without arguments. While the login() method has optional parameters (username: any = None, password: any = None), calling it without credentials will send None values to the API endpoint, which will likely fail. Either remove this line if it's unnecessary for the test, or pass config.user_log and config.user_pass as arguments.

🧹 Nitpick comments (6)
tests/api/test_auth_logout.py (2)

4-4: Unused imports.

LoginResponse and UserProfile are imported but never used in this file.

Suggested fix
-from tests.api.schemas.auth_schemas import LogoutResponse, ErrorResponse, LoginResponse, UserProfile
+from tests.api.schemas.auth_schemas import LogoutResponse, ErrorResponse

29-29: Missing Allure decorators on some test classes.

TestLogoutRefreshToken (line 29), TestLogoutRaceConditions (line 151), and TestLogoutQueryManipulation (lines 175-178) are missing @allure.feature and @allure.story decorators, unlike other classes in this file.

Also applies to: 151-151, 175-178

tests/api/test_auth_password.py (1)

51-54: Redundant status code check.

Line 52 asserts that status code is 400, then line 53 checks if response.status_code == 400 before validating. The conditional is unnecessary since the assertion would fail first.

Proposed fix
         assert response.status_code == 400, f"Expected 400, got {response.status_code}"
-        if response.status_code == 400:
-            ErrorResponse(**response.json())
+        ErrorResponse(**response.json())
tests/api/test_auth_username.py (3)

205-213: Same bug: Headers modified but not applied correctly.

The pattern here has the same issue as the previous test. Line 208 copies headers for restoration, but line 209 modifies the client headers directly. However, line 213 then restores the copy (which has original values), which is correct—but this is inconsistent with the pattern in the previous test.

For consistency and clarity, use the same save/modify/restore pattern across all tests.


215-223: Same pattern issue with header manipulation.

Line 218 copies headers, line 219 modifies client headers, but line 223 restores the copy. This is actually correct behavior but inconsistent with line 197-203. Consider extracting a context manager or fixture for header manipulation to ensure consistent save/restore.


181-193: Address TODO: Generate expired token dynamically.

The hardcoded expired_token (flagged by static analysis as S105) is brittle and could become invalid if JWT format requirements change. Consider extending utils/token_generator.py to generate expired tokens with valid structure but past expiration timestamps.

Would you like me to help implement an generate_expired_token() function in the token generator?

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6547b3f and 0dfe75d.

📒 Files selected for processing (8)
  • clients/api_client.py
  • tests/api/test_auth_audit_log.py
  • tests/api/test_auth_login.py
  • tests/api/test_auth_logout.py
  • tests/api/test_auth_password.py
  • tests/api/test_auth_profile.py
  • tests/api/test_auth_refresh.py
  • tests/api/test_auth_username.py
🧰 Additional context used
🧬 Code graph analysis (5)
tests/api/test_auth_username.py (4)
tests/api/schemas/auth_schemas.py (2)
  • UserProfile (6-14)
  • ErrorResponse (33-34)
utils/token_generator.py (1)
  • generate_invalid_bearer_tokens (9-84)
fixtures/api_fixtures.py (3)
  • authenticated_auth_client (39-49)
  • valid_username (65-66)
  • valid_credentials (53-57)
clients/api_client.py (3)
  • change_username (185-186)
  • send_custom_request (86-100)
  • login (169-170)
tests/api/test_auth_password.py (4)
tests/api/schemas/auth_schemas.py (2)
  • ChangePasswordResponse (37-38)
  • ErrorResponse (33-34)
fixtures/api_fixtures.py (5)
  • authenticated_auth_client (39-49)
  • valid_credentials (53-57)
  • valid_password (69-70)
  • invalid_password (74-75)
  • faker (7-8)
clients/api_client.py (2)
  • change_password (182-183)
  • send_custom_request (86-100)
utils/token_generator.py (1)
  • generate_invalid_refresh_tokens (86-167)
tests/api/test_auth_audit_log.py (4)
tests/api/schemas/auth_schemas.py (2)
  • AuditLogResponse (51-55)
  • ErrorResponse (33-34)
utils/token_generator.py (1)
  • generate_invalid_bearer_tokens (9-84)
fixtures/api_fixtures.py (4)
  • authenticated_auth_client (39-49)
  • valid_credentials (53-57)
  • valid_username (65-66)
  • auth_client (32-35)
clients/api_client.py (5)
  • get_audit_log (188-190)
  • send_custom_request (86-100)
  • logout (175-177)
  • login (169-170)
  • change_username (185-186)
tests/api/test_auth_login.py (3)
tests/api/schemas/auth_schemas.py (3)
  • LoginResponse (17-21)
  • ErrorResponse (33-34)
  • UserProfile (6-14)
fixtures/api_fixtures.py (5)
  • valid_credentials (53-57)
  • auth_client (32-35)
  • invalid_username (61-62)
  • invalid_password (74-75)
  • invalid_credentials (79-83)
clients/api_client.py (4)
  • login (169-170)
  • refresh_token (172-173)
  • post (46-57)
  • get_profile (179-180)
clients/api_client.py (1)
config/settings.py (1)
  • Settings (6-78)
🪛 Gitleaks (8.30.0)
tests/api/test_auth_refresh.py

[high] 71-71: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)


[high] 178-178: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

🪛 Ruff (0.14.10)
tests/api/test_auth_profile.py

109-109: Local variable valid_roles is assigned to but never used

Remove assignment to unused variable valid_roles

(F841)


146-146: Possible hardcoded password assigned to: "expired_token"

(S105)

tests/api/test_auth_username.py

186-186: Possible hardcoded password assigned to: "expired_token"

(S105)

tests/api/test_auth_password.py

106-106: Undefined name WEAK_PASSWORD_CASES

(F821)


158-158: Undefined name generate_invalid_refresh_tokens

(F821)


174-174: Undefined name base64

(F821)


187-187: Undefined name base64

(F821)

tests/api/test_auth_refresh.py

71-71: Possible hardcoded password assigned to: "expired_token"

(S105)


178-178: Possible hardcoded password assigned to: "expired_token"

(S105)


191-191: Undefined name base64

(F821)


201-201: Undefined name base64

(F821)

tests/api/test_auth_login.py

91-91: Undefined name valid_credentials

(F821)


91-91: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


91-91: Undefined name valid_credentials

(F821)


92-92: Undefined name valid_credentials

(F821)


92-92: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


92-92: Undefined name valid_credentials

(F821)


93-93: Undefined name valid_credentials

(F821)


93-93: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


93-93: Undefined name valid_credentials

(F821)


94-94: Undefined name valid_credentials

(F821)


94-94: Undefined name valid_credentials

(F821)


94-94: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


95-95: Undefined name valid_credentials

(F821)


95-95: Undefined name valid_credentials

(F821)


95-95: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


96-96: Undefined name valid_credentials

(F821)


96-96: Undefined name valid_credentials

(F821)


96-96: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


97-97: Undefined name valid_credentials

(F821)


97-97: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


97-97: Undefined name valid_credentials

(F821)


97-97: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


98-98: Undefined name valid_credentials

(F821)


98-98: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


98-98: Undefined name valid_credentials

(F821)


98-98: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


99-99: Undefined name valid_credentials

(F821)


99-99: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


99-99: Undefined name valid_credentials

(F821)


99-99: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


109-109: Undefined name valid_credentials

(F821)

clients/api_client.py

100-100: Avoid specifying long messages outside the exception class

(TRY003)


169-169: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)


169-169: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: guardrails/scan
🔇 Additional comments (7)
tests/api/test_auth_refresh.py (1)

70-76: Hardcoded JWT is acceptable for expired token testing.

The Gitleaks warning about the JWT is a false positive - this is an intentionally expired test token with dummy data ("sub": "1234567890", "name": "John Doe"). However, consider adding a comment explaining this is a crafted test token.

tests/api/test_auth_logout.py (1)

147-149: Remove this comment—the tests are validating different scenarios.

test_logout_check_response_headers uses an authenticated client (logged-in state) while test_logout_check_cache uses an unauthenticated client (no token). These are testing different conditions, not the same endpoint behavior. Different Cache-Control values for different authentication states is expected and correct.

Likely an incorrect or invalid review comment.

tests/api/test_auth_password.py (1)

12-28: No action required - fixture mutation does not affect test isolation.

The valid_credentials fixture is function-scoped (default scope when not explicitly specified), meaning each test function receives its own independent dictionary instance. The mutation of valid_credentials["password"] in test_change_password_success only affects that specific test's copy and does not impact subsequent tests, as they receive fresh fixture instances. Test isolation is preserved.

Likely an incorrect or invalid review comment.

tests/api/test_auth_username.py (4)

98-111: Verify whether these special characters should truly be invalid.

Characters like ., _, -, and digits are commonly allowed in usernames. Expecting 400 for user.name, user_name, user-name, user123, and 123user seems overly restrictive. This ties back to the TODO on line 14—clarify the actual username requirements with the API specification before finalizing these test expectations.


251-251: application/json; charset=utf-8 may be a valid Content-Type.

Including application/json; charset=utf-8 in the "wrong content type" list might be incorrect—many APIs accept this as valid JSON content type. Verify whether the API under test specifically rejects this format.


260-266: LGTM: Cache header validation.

This test correctly validates that sensitive authentication endpoints return proper cache-control headers to prevent caching of responses.


18-27: Address the TODO comment on line 14 to clarify username validation requirements.

The TODO on line 14 should be resolved before finalizing these tests to ensure username validation requirements are clearly documented.

Note: The mutation of valid_credentials on line 25 (valid_credentials["username"] = valid_username) is not a test isolation issue since the fixture uses the default function scope, ensuring each test receives a fresh instance.

Likely an incorrect or invalid review comment.

Comment on lines 1 to 7
import pytest
import allure
from pydantic import ValidationError
from tests.api.schemas.auth_schemas import ChangePasswordResponse, ErrorResponse
from tests.api.cases.test_cases import EMPTY_STRING_CASES, NONSTRING_CASES

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing imports: WEAK_PASSWORD_CASES, generate_invalid_refresh_tokens, and base64.

Static analysis confirms these names are undefined. The file needs additional imports.

Proposed fix
 import pytest
 import allure
+import base64
 from pydantic import ValidationError
 from tests.api.schemas.auth_schemas import ChangePasswordResponse, ErrorResponse
-from tests.api.cases.test_cases import EMPTY_STRING_CASES, NONSTRING_CASES
+from tests.api.cases.test_cases import EMPTY_STRING_CASES, NONSTRING_CASES, WEAK_PASSWORD_CASES
+from utils.token_generator import generate_invalid_refresh_tokens
🤖 Prompt for AI Agents
In @tests/api/test_auth_password.py around lines 1 - 6, The test file imports
are missing WEAK_PASSWORD_CASES, generate_invalid_refresh_tokens, and base64
which causes NameError/undefined symbol failures; add imports at the top of the
file: import base64, import WEAK_PASSWORD_CASES from its test data module (same
place as EMPTY_STRING_CASES/NONSTRING_CASES), and import
generate_invalid_refresh_tokens from the helper/case utilities where other
generator helpers live so the symbols WEAK_PASSWORD_CASES,
generate_invalid_refresh_tokens, and base64 are defined before they are used in
the tests.

return response

@allure.step("Custom Request {method} {endpoint}")
def send_custom_request(self, method: str, endpoint: str,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

methods like get(), post() accept optional headers, but send_custom_request() doesn't.

@pytest.fixture
def invalid_username():
return fake.email()
def authenticated_auth_client(config: Settings):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple tests modify client.token, client.headers, call logout(), etc. With session scope, one test's modifications affect all subsequent tests.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically after check with modified token/headers it returns to valid state, but in any case i'll add func scope.


assert response.status_code == 200
#TODO:Clarify expected cors headers
assert response.headers["Access-Control-Allow-Origin"] == "*"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true is invalid

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just a stab, not real check. Thats why i left todo:
#TODO:Clarify expected cors headers

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🤖 Fix all issues with AI agents
In @clients/api_client.py:
- Around line 186-187: The instance attribute self.refresh_token defined in
APIClient.__init__ is being shadowed by the method def refresh_token(self,
refresh_token: str) -> httpx.Response; rename the method (e.g., to
refresh_access_token or post_refresh_token) or rename the stored attribute
(e.g., to self._refresh_token or self.stored_refresh_token) so they do not
collide; update all internal references to the renamed symbol (e.g., calls to
client.refresh_token(...) -> client.refresh_access_token(...), or usages of the
stored token to self._refresh_token) and ensure the method still calls
self.post("/v1/auth/refresh", json={"refresh_token": refresh_token}).

In @tests/api/test_auth_audit_log.py:
- Around line 150-163: The test test_audit_log_entry_structure_with_user_actions
mutates the shared fixture valid_credentials by assigning
valid_credentials["username"] = valid_username; avoid mutating the fixture by
creating and using a local copy (e.g., creds = valid_credentials.copy()) and
update creds["username"] instead, then use creds for login; alternatively ensure
the valid_credentials fixture is function-scoped so each test gets a fresh
dict—pick one approach and apply it in the test function to prevent cross-test
pollution.
- Around line 190-198: The test modifies a local headers copy but calls
authenticated_auth_client.get_audit_log() before applying them; update
test_get_audit_log_with_wrong_auth_type to save the original =
authenticated_auth_client.headers, create the wrong-auth headers, assign
authenticated_auth_client.headers = headers before calling
authenticated_auth_client.get_audit_log(), perform the assertions and
ErrorResponse check, then restore authenticated_auth_client.headers = original
at the end so the client is returned to its prior state.

In @tests/api/test_auth_password.py:
- Around line 197-208: In test_change_password_with_too_long_access_token the
code mutates a local headers copy but never applies it to the client, so the
request uses the original headers; fix by saving the original_headers =
authenticated_auth_client.headers.copy(), then set
authenticated_auth_client.headers = headers (the modified copy with the long
Authorization) before calling authenticated_auth_client.change_password(...),
assert the response and parse ErrorResponse, and finally restore
authenticated_auth_client.headers = original_headers after the test.
- Around line 244-255: The test test_change_password_without_content_type
creates a local copy of headers and pops "Content-Type" but never applies it to
authenticated_auth_client, so the request still sends the original headers;
update the code to set authenticated_auth_client.headers = headers (or pass
headers=headers into authenticated_auth_client.change_password) before calling
change_password so the modified headers without "Content-Type" are actually
used, and ensure you restore the original headers after the request if needed.
- Around line 257-269: The test test_change_password_with_wrong_content_type
creates a copy of authenticated_auth_client.headers into headers and modifies
Content-Type but never applies it to authenticated_auth_client before calling
change_password, so the request uses the original headers; before calling
authenticated_auth_client.change_password set authenticated_auth_client.headers
= headers (and restore original headers afterwards if needed) so the modified
Content-Type is actually sent with the request.
- Around line 1-6: The test module is missing imports for WEAK_PASSWORD_CASES
and generate_invalid_refresh_tokens which causes undefined name errors; add
imports for WEAK_PASSWORD_CASES from the module that defines weak password test
cases (the same module that defines EMPTY_STRING_CASES / NONSTRING_CASES) and
import generate_invalid_refresh_tokens from its helper module (the module that
provides invalid token generators), then update the import block at the top of
the file to include these two names so the parametrize decorators referencing
WEAK_PASSWORD_CASES and generate_invalid_refresh_tokens() resolve.

In @tests/api/test_auth_profile.py:
- Around line 206-215: The test test_get_profile_with_wrong_content_type
modifies a local headers copy but calls authenticated_auth_client.get_profile()
before applying them; assign the modified headers to
authenticated_auth_client.headers before calling
authenticated_auth_client.get_profile() so the request uses the intended
Content-Type, and (optionally) restore the original headers after the request to
avoid side effects.
- Around line 101-111: The test test_profile_tenant_role defines valid_roles but
never uses it; update the assertions in that test to validate tenant_role
membership instead of only non-empty: keep or replace the current assert
profile.tenant_role check with an assertion like assert profile.tenant_role in
valid_roles (and include a helpful message), ensuring you reference the
valid_roles list and the UserProfile instance (profile.tenant_role) when making
the assertion.
- Around line 130-139: test_get_profile_invalid_token mutates
authenticated_auth_client.token without restoring it which can leak state across
parametrized iterations; capture the original token before setting
authenticated_auth_client.token = invalid_token and ensure you restore it after
the request (e.g., in a finally block) so authenticated_auth_client.token is
reset for subsequent iterations and cleanup; reference the test function
test_get_profile_invalid_token and the authenticated_auth_client.token attribute
when implementing the fix.

In @tests/api/test_auth_refresh.py:
- Around line 242-251: The test test_refresh_with_wrong_content_type copies and
mutates headers but never applies them to the client before calling
authenticated_auth_client.refresh_token, so the request uses the original
headers; fix by assigning the mutated headers back to
authenticated_auth_client.headers before invoking refresh_token on
authenticated_auth_client.refresh_token, then (as the test already does) restore
the original headers afterward to avoid side effects.
- Around line 198-206: The test test_refresh_with_wrong_auth_format modifies a
local headers copy but never applies it to the client before calling
authenticated_auth_client.refresh_token, so the request still uses the original
headers; fix by assigning the modified headers back to
authenticated_auth_client.headers (or passing headers into refresh_token if that
method accepts a headers param) before invoking
authenticated_auth_client.refresh_token(authenticated_auth_client.refresh_token)
so the "Authorization" header with the base64 token is actually used.
🧹 Nitpick comments (3)
clients/api_client.py (1)

99-114: Missing headers parameter forwarding in send_custom_request.

The method accepts json and params but doesn't accept or forward the headers parameter to the delegated methods. This limits flexibility when callers need custom headers.

♻️ Suggested fix
 @allure.step("Custom Request {method} {endpoint}")
 def send_custom_request(self, method: str, endpoint: str, 
                        json: Optional[Dict[str, Any]] = None,
-                       params: Optional[Dict[str, Any]] = None) -> httpx.Response:
+                       params: Optional[Dict[str, Any]] = None,
+                       headers: Optional[Dict[str, str]] = None) -> httpx.Response:
     if method.upper() == "GET":
-        return self.get(endpoint, params=params)
+        return self.get(endpoint, params=params, headers=headers)
     elif method.upper() == "POST":
-        return self.post(endpoint, json=json)
+        return self.post(endpoint, json=json, headers=headers)
     elif method.upper() == "PUT":
-        return self.put(endpoint, json=json)
+        return self.put(endpoint, json=json, headers=headers)
     elif method.upper() == "PATCH":
-        return self.patch(endpoint, json=json)
+        return self.patch(endpoint, json=json, headers=headers)
     elif method.upper() == "DELETE":
-        return self.delete(endpoint)
+        return self.delete(endpoint, headers=headers)
     else:
         raise ValueError(f"Unsupported HTTP method: {method}")
tests/api/test_auth_password.py (1)

53-55: Redundant conditional check after assertion.

The if response.status_code == 400 check on line 54 is redundant because the assertion on line 53 already guarantees the status code is 400.

♻️ Suggested fix
     assert response.status_code == 400, f"Expected 400, got {response.status_code}"
-    if response.status_code == 400:
-        ErrorResponse(**response.json())
+    ErrorResponse(**response.json())
tests/api/test_auth_refresh.py (1)

79-100: Consider using token-based differentiation instead of time.sleep.

The time.sleep(1) on line 91 assumes tokens will differ due to timestamp-based claims. This is fragile and slows down tests. If tokens include a jti (JWT ID) claim, they should already be unique without needing a delay.

If time-based differentiation is required by the token implementation, consider mocking time instead of sleeping in tests.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0dfe75d and 95622f8.

📒 Files selected for processing (5)
  • clients/api_client.py
  • tests/api/test_auth_audit_log.py
  • tests/api/test_auth_password.py
  • tests/api/test_auth_profile.py
  • tests/api/test_auth_refresh.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-12T13:38:40.404Z
Learnt from: 0xc7c5
Repo: chainstack/cp-autotests PR: 1
File: tests/api/test_auth_refresh.py:187-195
Timestamp: 2026-01-12T13:38:40.404Z
Learning: In tests/api/test_auth_refresh.py, the header manipulation pattern `headers = client.headers.copy()` followed by `client.headers["Authorization"] = ...` and later `client.headers = headers` is correct: it backs up, modifies the client's headers directly, then restores them.

Applied to files:

  • clients/api_client.py
  • tests/api/test_auth_profile.py
  • tests/api/test_auth_refresh.py
  • tests/api/test_auth_audit_log.py
  • tests/api/test_auth_password.py
🧬 Code graph analysis (4)
clients/api_client.py (1)
config/settings.py (1)
  • Settings (6-78)
tests/api/test_auth_profile.py (3)
tests/api/schemas/auth_schemas.py (3)
  • UserProfile (6-14)
  • ErrorResponse (33-34)
  • LoginResponse (17-21)
fixtures/api_fixtures.py (2)
  • authenticated_auth_client (39-49)
  • valid_credentials (53-57)
clients/api_client.py (3)
  • get_profile (193-194)
  • login (183-184)
  • logout (189-191)
tests/api/test_auth_refresh.py (4)
tests/api/schemas/auth_schemas.py (4)
  • LoginResponse (17-21)
  • RefreshTokenResponse (24-26)
  • ErrorResponse (33-34)
  • UserProfile (6-14)
utils/token_generator.py (2)
  • generate_invalid_refresh_tokens (86-167)
  • generate_invalid_bearer_tokens (9-84)
fixtures/api_fixtures.py (3)
  • auth_client (32-35)
  • valid_credentials (53-57)
  • authenticated_auth_client (39-49)
clients/api_client.py (5)
  • login (183-184)
  • refresh_token (186-187)
  • post (46-57)
  • get_profile (193-194)
  • logout (189-191)
tests/api/test_auth_password.py (4)
tests/api/schemas/auth_schemas.py (2)
  • ChangePasswordResponse (37-38)
  • ErrorResponse (33-34)
fixtures/api_fixtures.py (4)
  • authenticated_auth_client (39-49)
  • valid_credentials (53-57)
  • valid_password (69-70)
  • faker (7-8)
clients/api_client.py (2)
  • change_password (196-197)
  • send_custom_request (100-114)
utils/token_generator.py (1)
  • generate_invalid_refresh_tokens (86-167)
🪛 Gitleaks (8.30.0)
tests/api/test_auth_profile.py

[high] 146-146: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

tests/api/test_auth_refresh.py

[high] 72-72: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)


[high] 179-179: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

🪛 Ruff (0.14.10)
clients/api_client.py

114-114: Avoid specifying long messages outside the exception class

(TRY003)

tests/api/test_auth_profile.py

109-109: Local variable valid_roles is assigned to but never used

Remove assignment to unused variable valid_roles

(F841)


146-146: Possible hardcoded password assigned to: "expired_token"

(S105)

tests/api/test_auth_refresh.py

72-72: Possible hardcoded password assigned to: "expired_token"

(S105)


179-179: Possible hardcoded password assigned to: "expired_token"

(S105)

tests/api/test_auth_password.py

107-107: Undefined name WEAK_PASSWORD_CASES

(F821)


159-159: Undefined name generate_invalid_refresh_tokens

(F821)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: guardrails/scan
🔇 Additional comments (13)
clients/api_client.py (3)

85-97: LGTM!

The patch method is consistent with the other HTTP method implementations, properly using Allure steps for reporting and following the same header handling pattern.


116-124: LGTM!

Cookie management methods are cleanly implemented and provide useful utilities for test scenarios requiring cookie manipulation.


174-204: All endpoint paths and HTTP methods in AuthAPIClient correctly match the OpenAPI specification—no changes needed.

tests/api/test_auth_audit_log.py (4)

14-30: LGTM!

The general audit log test properly validates response schema and pagination defaults. Good use of Pydantic for schema validation.


49-58: Verify expected behavior for values exceeding 64-bit integer range.

Testing MAX_64_BIT_INT+1 may cause integer overflow or API rejection depending on the backend. The assertion audit_log.page == page assumes the API accepts and echoes back overflow values, which seems unlikely.

Consider whether the expected behavior is a 400 error for out-of-range values rather than a successful 200.


229-236: LGTM!

Good test coverage for response header validation ensuring sensitive data isn't cached.


200-208: Bug: Modified headers not applied to client before request.

Same issue as test_get_audit_log_with_wrong_auth_type. The headers dict copy is modified but the client's actual headers remain unchanged during the request.

🐛 Proposed fix
 def test_get_audit_log_with_wrong_auth_format(self, authenticated_auth_client):
     headers = authenticated_auth_client.headers.copy() 
     authenticated_auth_client.headers["Authorization"] = "Bearer " + base64.b64encode(authenticated_auth_client.token.encode()).decode() 
     response = authenticated_auth_client.get_audit_log()       
     assert response.status_code == 401, f"Expected 401, got {response.status_code}"
     ErrorResponse(**response.json())
     authenticated_auth_client.headers = headers

This one is already correct per the learned pattern - disregard this comment if the code at line 204 shows authenticated_auth_client.headers["Authorization"] being modified directly.

⛔ Skipped due to learnings
Learnt from: 0xc7c5
Repo: chainstack/cp-autotests PR: 1
File: tests/api/test_auth_refresh.py:187-195
Timestamp: 2026-01-12T13:38:40.404Z
Learning: In tests/api/test_auth_refresh.py, the header manipulation pattern `headers = client.headers.copy()` followed by `client.headers["Authorization"] = ...` and later `client.headers = headers` is correct: it backs up, modifies the client's headers directly, then restores them.
tests/api/test_auth_profile.py (2)

141-153: Hardcoded JWT is acceptable for expired token testing.

The Gitleaks warning is a false positive in this context—this is an intentionally expired/invalid JWT for testing purposes. The TODO comment indicates plans to generate these dynamically.


196-204: Verify that GET requests require Content-Type header.

It's unusual for a server to require Content-Type on GET requests since they typically don't have a body. This test expects a 400 when Content-Type is missing.

Confirm this is the intended API behavior. If the API doesn't actually require Content-Type for GET requests, this test will fail or test incorrect behavior.

tests/api/test_auth_refresh.py (4)

14-35: LGTM!

Comprehensive test for successful token refresh with proper schema validation and verification that the new access token differs from the original.


67-77: Hardcoded expired JWT is acceptable for testing.

The Gitleaks warning is a false positive—this is an intentionally expired JWT for testing the expired token scenario. The TODO comment indicates plans to generate these dynamically.


124-142: Verify expected behavior: old access tokens invalidated after refresh.

This test assumes the API invalidates old access tokens when a new token is issued via refresh. This is not universal behavior—some APIs allow both old and new tokens to remain valid until expiration.

Confirm with the API specification whether token refresh should invalidate the previous access token. If not, this test's expectation of 401 would be incorrect.


253-259: LGTM!

Good test for verifying cache-control headers on token refresh responses to prevent sensitive token data from being cached.

Comment on lines +1 to +6
import pytest
import allure
from pydantic import ValidationError
from tests.api.schemas.auth_schemas import ChangePasswordResponse, ErrorResponse
from tests.api.cases.test_cases import EMPTY_STRING_CASES, NONSTRING_CASES
import base64
Copy link

@coderabbitai coderabbitai bot Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if WEAK_PASSWORD_CASES exists in test_cases
echo "=== Searching for WEAK_PASSWORD_CASES ==="
rg "WEAK_PASSWORD_CASES" tests/api/cases/ 2>/dev/null | head -20

echo ""
echo "=== Searching for generate_invalid_refresh_tokens ==="
rg "generate_invalid_refresh_tokens" --glob="*.py" 2>/dev/null | head -20

echo ""
echo "=== Checking test_auth_password.py line 107 context ==="
sed -n '100,115p' tests/api/test_auth_password.py 2>/dev/null

echo ""
echo "=== Checking test_auth_password.py line 159 context ==="
sed -n '155,165p' tests/api/test_auth_password.py 2>/dev/null

echo ""
echo "=== Checking imports in test_auth_password.py ==="
head -20 tests/api/test_auth_password.py 2>/dev/null

Repository: chainstack/cp-autotests

Length of output: 3322


Add missing imports WEAK_PASSWORD_CASES and generate_invalid_refresh_tokens.

Static analysis correctly identifies undefined names causing test collection failure:

  • Line 107 uses WEAK_PASSWORD_CASES in parametrize decorator
  • Line 159 uses generate_invalid_refresh_tokens() in parametrize decorator

Both are defined in their respective modules but not imported.

🐛 Proposed fix
 import pytest
 import allure
 from pydantic import ValidationError
 from tests.api.schemas.auth_schemas import ChangePasswordResponse, ErrorResponse
-from tests.api.cases.test_cases import EMPTY_STRING_CASES, NONSTRING_CASES
+from tests.api.cases.test_cases import EMPTY_STRING_CASES, NONSTRING_CASES, WEAK_PASSWORD_CASES
+from utils.token_generator import generate_invalid_refresh_tokens
 import base64
🤖 Prompt for AI Agents
In @tests/api/test_auth_password.py around lines 1 - 6, The test module is
missing imports for WEAK_PASSWORD_CASES and generate_invalid_refresh_tokens
which causes undefined name errors; add imports for WEAK_PASSWORD_CASES from the
module that defines weak password test cases (the same module that defines
EMPTY_STRING_CASES / NONSTRING_CASES) and import generate_invalid_refresh_tokens
from its helper module (the module that provides invalid token generators), then
update the import block at the top of the file to include these two names so the
parametrize decorators referencing WEAK_PASSWORD_CASES and
generate_invalid_refresh_tokens() resolve.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats because such entities will be implemented when credentials requirements be clarified.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0xc7c5, understood! Since these will be implemented once the credentials requirements are clarified, you might want to consider:

  1. Adding TODO comments near lines 107 and 159 to document this intention
  2. Potentially skipping or marking these specific test methods with @pytest.mark.skip(reason="Pending credentials requirements") until the implementations are ready

This will prevent test collection failures in the meantime while preserving the test structure for future implementation.

@chainstack chainstack deleted a comment from coderabbitai bot Jan 12, 2026
@chainstack chainstack deleted a comment from coderabbitai bot Jan 12, 2026
@chainstack chainstack deleted a comment from coderabbitai bot Jan 12, 2026
@chainstack chainstack deleted a comment from coderabbitai bot Jan 12, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🤖 Fix all issues with AI agents
In @fixtures/api_fixtures.py:
- Around line 38-49: The fixture authenticated_auth_client silently continues
when client.login fails, leaving tests to hit confusing 401s; change it to fail
fast by checking the login response (from client.login called in
authenticated_auth_client) and raise an explicit error (e.g., pytest.fail or
RuntimeError) or log and raise if response.status_code != 200 or
token/refresh_token are missing, ensuring the client is closed before raising so
resources are cleaned up.

In @tests/api/test_auth_audit_log.py:
- Around line 220-227: The test calls authenticated_auth_client.login() with no
credentials after logout which will fail; update the
test_get_audit_log_with_revoked_access_token to restore authentication properly
by calling authenticated_auth_client.login(...) with valid credentials (e.g.,
pass the same username/password used to create the fixture) or by using a helper
on the client that reuses stored credentials (e.g.,
authenticated_auth_client.reauthenticate() or
authenticated_auth_client.login(saved_username, saved_password)), so the client
is properly authenticated again for subsequent tests.
- Around line 150-163: The test mutates the shared valid_credentials fixture and
calls authenticated_auth_client.login() without explicit credentials; instead,
avoid mutating the fixture by creating a local copy (e.g., creds =
dict(valid_credentials)) and set creds["username"] = valid_username, use creds
when calling authenticated_auth_client.login(creds["username"],
creds["password"]) after logout, and keep change_username(valid_username) as-is;
update any subsequent references to use the local creds rather than modifying
valid_credentials.

In @tests/api/test_auth_password.py:
- Line 171: Fix the typo in the test's Allure title decorator: update the
@allure.title string on the test in tests/api/test_auth_password.py from "Change
password with with wrong auth type" to "Change password with wrong auth type"
(locate the @allure.title decorator for the test and remove the duplicate
"with").
- Around line 114-115: The assertion message in tests/api/test_auth_password.py
is inconsistent: it claims "Expected 400 or 501" but the assertion only checks
for 400; update the test to either (a) change the assertion message to "Expected
400" to match the current check, or (b) expand the assertion to allow both
status codes (e.g., assert response.status_code in (400, 501)) and keep the
existing message; ensure you still call ErrorResponse(**response.json()) against
the response object so the error payload is validated.
- Around line 43-44: The assertion in the test uses "response.status_code== 400"
with no space before the equality operator; update the assertion to use proper
spacing around the == operator for readability (modify the assertion referencing
response.status_code in tests/api/test_auth_password.py) while leaving the
subsequent ErrorResponse(**response.json()) line unchanged.
- Around line 246-255: The test_change_password_without_content_type test copies
headers to a local variable and pops Content-Type from the copy but never
applies it to authenticated_auth_client, so change_password() still sends the
original headers; fix by saving the original headers (e.g., orig =
authenticated_auth_client.headers.copy()), then remove "Content-Type" directly
from authenticated_auth_client.headers before calling
authenticated_auth_client.change_password(...), assert the response, and finally
restore the original headers by assigning orig back to
authenticated_auth_client.headers.
- Around line 1-6: The test file is missing two imports which cause NameError at
runtime: import WEAK_PASSWORD_CASES from the test cases module (where
EMPTY_STRING_CASES and NONSTRING_CASES are imported) and import
generate_invalid_refresh_tokens from the token generator module (where the
refresh token helpers live); add these two imports at the top of
tests/api/test_auth_password.py and verify WEAK_PASSWORD_CASES is defined in the
test_cases module (if not, define it there) so references to WEAK_PASSWORD_CASES
and generate_invalid_refresh_tokens used in the tests resolve.
- Around line 260-269: The test_change_password_with_wrong_content_type test
modifies a local headers copy but never applies it to the client or the request,
so the wrong Content-Type is not actually sent; update the test to either set
authenticated_auth_client.headers = headers before calling
authenticated_auth_client.change_password or pass headers=headers into
authenticated_auth_client.change_password so the modified headers
(headers["Content-Type"] = content_type) are used for the request and then
restore the original headers after the call.

In @tests/api/test_auth_profile.py:
- Around line 185-192: The test calls authenticated_auth_client.login() with no
arguments which sends None credentials and fails; update
test_get_profile_with_revoked_access_token so the re-login supplies valid
credentials (e.g., call authenticated_auth_client.login(username, password) with
the fixture or module test credentials or use the fixture's provided reauth
helper if available) after ErrorResponse(**response.json()) to restore a valid
session instead of calling login() with no args.
- Line 5: The test imports a non-existent symbol generate_invalid_tokens;
replace that import with the actual function name generate_invalid_bearer_tokens
from utils.token_generator and update any call sites in
tests/api/test_auth_profile.py that reference generate_invalid_tokens to call
generate_invalid_bearer_tokens instead so the test imports and uses the correct
function name.

In @tests/api/test_auth_refresh.py:
- Around line 218-225: The test test_refresh_with_revoked_refresh_token calls
authenticated_auth_client.login() with no args after logout which will fail;
update the teardown to re-authenticate by calling login with credentials (e.g.,
authenticated_auth_client.login(authenticated_auth_client.username,
authenticated_auth_client.password)) or recreate the fixture before finishing
the test so post-logout re-login supplies the required credentials instead of a
no-arg call.
- Around line 198-206: The test modifies a local headers copy but never applies
it before calling post_refresh, so it doesn't exercise the wrong auth format;
update authenticated_auth_client.headers with the modified headers (or pass
headers into authenticated_auth_client.post_refresh if that method accepts a
headers param) before calling post_refresh in
test_refresh_with_wrong_auth_format, and restore the original headers after the
request to avoid side effects on other tests.
🧹 Nitpick comments (4)
fixtures/api_fixtures.py (1)

68-75: valid_password and invalid_password use identical generation logic.

Both fixtures call faker.password(), making the naming distinction misleading. If these are intended to represent passwords that pass/fail validation rules, consider differentiating them (e.g., by length, character requirements).

tests/api/test_auth_audit_log.py (1)

49-58: 64-bit integer overflow test may have unexpected behavior.

MAX_64_BIT_INT+1 exceeds the maximum signed 64-bit integer. The test asserts that the response echoes these values back (audit_log.page == page), but integer overflow handling varies by backend. If the server clamps or rejects overflow values, this assertion will fail.

Consider separating overflow cases into a distinct test expecting 400/422 errors.

tests/api/test_auth_refresh.py (1)

79-100: time.sleep(1) may be insufficient or unnecessary.

The sleep ensures tokens have different timestamps, but:

  1. If iat has second precision, 1 second should suffice
  2. If the API uses sub-second precision or includes randomness, sleep is unnecessary
  3. This adds test latency

Consider verifying if the sleep is actually needed based on token generation behavior.

tests/api/test_auth_password.py (1)

53-55: Redundant conditional check after assertion.

The if response.status_code == 400: guard is unnecessary—if the assertion on line 53 passes, the condition is always true; if it fails, this code is never reached.

Proposed fix
         assert response.status_code == 400, f"Expected 400, got {response.status_code}"
-        if response.status_code == 400:
-            ErrorResponse(**response.json())
+        ErrorResponse(**response.json())
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 95622f8 and ebf2437.

📒 Files selected for processing (6)
  • clients/api_client.py
  • fixtures/api_fixtures.py
  • tests/api/test_auth_audit_log.py
  • tests/api/test_auth_password.py
  • tests/api/test_auth_profile.py
  • tests/api/test_auth_refresh.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-12T13:38:40.404Z
Learnt from: 0xc7c5
Repo: chainstack/cp-autotests PR: 1
File: tests/api/test_auth_refresh.py:187-195
Timestamp: 2026-01-12T13:38:40.404Z
Learning: In tests/api/test_auth_refresh.py, the header manipulation pattern `headers = client.headers.copy()` followed by `client.headers["Authorization"] = ...` and later `client.headers = headers` is correct: it backs up, modifies the client's headers directly, then restores them.

Applied to files:

  • fixtures/api_fixtures.py
  • tests/api/test_auth_profile.py
  • clients/api_client.py
  • tests/api/test_auth_audit_log.py
  • tests/api/test_auth_refresh.py
  • tests/api/test_auth_password.py
🧬 Code graph analysis (5)
fixtures/api_fixtures.py (2)
clients/api_client.py (3)
  • AuthAPIClient (175-205)
  • login (184-185)
  • get (32-43)
config/settings.py (1)
  • Settings (6-78)
tests/api/test_auth_profile.py (3)
tests/api/schemas/auth_schemas.py (3)
  • UserProfile (6-14)
  • ErrorResponse (33-34)
  • LoginResponse (17-21)
fixtures/api_fixtures.py (2)
  • authenticated_auth_client (39-49)
  • valid_credentials (53-57)
clients/api_client.py (2)
  • get_profile (194-195)
  • login (184-185)
tests/api/test_auth_audit_log.py (4)
tests/api/schemas/auth_schemas.py (2)
  • AuditLogResponse (51-55)
  • ErrorResponse (33-34)
utils/token_generator.py (1)
  • generate_invalid_bearer_tokens (9-84)
fixtures/api_fixtures.py (3)
  • authenticated_auth_client (39-49)
  • valid_credentials (53-57)
  • auth_client (32-35)
clients/api_client.py (6)
  • get_audit_log (203-205)
  • send_custom_request (100-115)
  • logout (190-192)
  • login (184-185)
  • change_username (200-201)
  • get_profile (194-195)
tests/api/test_auth_refresh.py (4)
tests/api/schemas/auth_schemas.py (4)
  • LoginResponse (17-21)
  • RefreshTokenResponse (24-26)
  • ErrorResponse (33-34)
  • UserProfile (6-14)
utils/token_generator.py (2)
  • generate_invalid_refresh_tokens (86-167)
  • generate_invalid_bearer_tokens (9-84)
fixtures/api_fixtures.py (3)
  • auth_client (32-35)
  • valid_credentials (53-57)
  • authenticated_auth_client (39-49)
clients/api_client.py (5)
  • login (184-185)
  • post_refresh (187-188)
  • post (46-57)
  • get_profile (194-195)
  • logout (190-192)
tests/api/test_auth_password.py (4)
tests/api/schemas/auth_schemas.py (2)
  • ChangePasswordResponse (37-38)
  • ErrorResponse (33-34)
fixtures/api_fixtures.py (5)
  • authenticated_auth_client (39-49)
  • valid_credentials (53-57)
  • valid_password (69-70)
  • invalid_password (74-75)
  • faker (7-8)
clients/api_client.py (2)
  • change_password (197-198)
  • send_custom_request (100-115)
utils/token_generator.py (1)
  • generate_invalid_refresh_tokens (86-167)
🪛 Gitleaks (8.30.0)
tests/api/test_auth_refresh.py

[high] 72-72: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)


[high] 179-179: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

🪛 Ruff (0.14.10)
tests/api/test_auth_profile.py

146-146: Possible hardcoded password assigned to: "expired_token"

(S105)

clients/api_client.py

115-115: Avoid specifying long messages outside the exception class

(TRY003)

tests/api/test_auth_refresh.py

72-72: Possible hardcoded password assigned to: "expired_token"

(S105)


179-179: Possible hardcoded password assigned to: "expired_token"

(S105)

tests/api/test_auth_password.py

107-107: Undefined name WEAK_PASSWORD_CASES

(F821)


159-159: Undefined name generate_invalid_refresh_tokens

(F821)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: guardrails/scan
🔇 Additional comments (17)
fixtures/api_fixtures.py (2)

52-57: LGTM!

The valid_credentials fixture correctly sources credentials from config.


6-8: LGTM!

Session-scoped faker fixture is appropriate for consistent test data generation.

clients/api_client.py (4)

85-97: LGTM!

The patch method follows the same pattern as other HTTP methods with proper Allure integration.


99-115: LGTM with minor note.

The send_custom_request method provides a clean dispatch mechanism. The static analysis hint (TRY003) about the long exception message is a style nit that can be safely ignored here since the message is contextually appropriate for debugging.


175-205: LGTM!

AuthAPIClient is well-structured with clear endpoint mappings. The methods appropriately delegate to the base class HTTP methods.


9-15: This concern is unfounded — self.headers caching does not cause authentication issues.

All HTTP methods (get, post, put, delete, patch) call _get_headers() fresh on each request, not the cached self.headers. The self.headers attribute exists solely for tests to directly manipulate headers via the backup/restore pattern (as correctly shown in test_auth_refresh.py), and does not affect actual authentication or API calls. No changes needed.

Likely an incorrect or invalid review comment.

tests/api/test_auth_audit_log.py (2)

14-30: LGTM!

Well-structured smoke test with proper schema validation and meaningful assertions.


168-186: LGTM!

Good coverage of authentication edge cases. The header backup/restore pattern is correct per retrieved learnings.

tests/api/test_auth_profile.py (3)

141-153: Hardcoded expired JWT is acceptable for test purposes.

The static analysis flag (S105) about hardcoded password is a false positive — this is an intentionally invalid/expired test token, not a real credential. The TODO comment indicates plans to generate these dynamically.


12-31: LGTM!

Good smoke test coverage with schema validation. The commented-out tenant assertions (lines 27-29) suggest these fields may be optional or not yet implemented.


196-215: Verify API design: GET requests require Content-Type header.

These tests assert that GET /v1/auth/profile returns 400 when Content-Type is missing or incorrect. This is unusual—GET requests typically don't require Content-Type since they have no body (per RFC 7231). However, the API client and multiple endpoints in the suite (POST /v1/auth/login, POST /v1/auth/logout, POST /v1/auth/refresh, PUT /v1/auth/password) all enforce this validation universally. If this is intentional API design, consider documenting it in the OpenAPI spec. If not, reconsider whether Content-Type validation is necessary for GET requests.

tests/api/test_auth_refresh.py (3)

67-77: Hardcoded expired JWT is acceptable for tests.

The static analysis (S105) and Gitleaks flags are false positives — these are intentionally invalid test tokens with a TODO to generate them dynamically. Same applies to line 179.


124-142: Verify that old access tokens are invalidated after refresh.

This test assumes the API invalidates old access tokens upon refresh (token rotation). This is a security best practice but not universal. If the API allows multiple valid access tokens concurrently, this test will fail.


14-35: LGTM!

Solid smoke test for token refresh flow with proper schema validation.

tests/api/test_auth_password.py (3)

17-29: LGTM with a note on credential state.

The test correctly validates the password change flow and updates valid_credentials["password"] to track the new password. Be aware that if valid_credentials fixture has session/module scope, this mutation could affect subsequent tests.


206-208: Potential failure when parsing 431 response body.

HTTP 431 (Request Header Fields Too Large) responses often don't include a JSON body—the server may close the connection immediately. The ErrorResponse(**response.json()) call could fail with a JSON decode error.

Consider guarding the schema validation:

         assert response.status_code == 431, f"Expected 431, got {response.status_code}"
-        ErrorResponse(**response.json())
+        if response.content:
+            ErrorResponse(**response.json())

271-279: Inconsistent Cache-Control header expectations.

test_change_password_check_response_headers (line 218) expects Cache-Control: "no-store", while this test expects Cache-Control: "no-cache, no-store, must-revalidate". Both test the same successful password change endpoint. Verify the actual API response and align the assertions.

Comment on lines +114 to +115
assert response.status_code == 400, f"Expected 400 or 501, got {response.status_code}"
ErrorResponse(**response.json())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Assertion message inconsistent with actual check.

The message says "Expected 400 or 501" but the assertion only checks for 400. Update either the message or the assertion logic.

Option 1: Fix message
-        assert response.status_code == 400, f"Expected 400 or 501, got {response.status_code}"
+        assert response.status_code == 400, f"Expected 400, got {response.status_code}"
Option 2: Allow both status codes
-        assert response.status_code == 400, f"Expected 400 or 501, got {response.status_code}"
+        assert response.status_code in (400, 501), f"Expected 400 or 501, got {response.status_code}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert response.status_code == 400, f"Expected 400 or 501, got {response.status_code}"
ErrorResponse(**response.json())
assert response.status_code == 400, f"Expected 400, got {response.status_code}"
ErrorResponse(**response.json())
Suggested change
assert response.status_code == 400, f"Expected 400 or 501, got {response.status_code}"
ErrorResponse(**response.json())
assert response.status_code in (400, 501), f"Expected 400 or 501, got {response.status_code}"
ErrorResponse(**response.json())
🤖 Prompt for AI Agents
In @tests/api/test_auth_password.py around lines 114 - 115, The assertion
message in tests/api/test_auth_password.py is inconsistent: it claims "Expected
400 or 501" but the assertion only checks for 400; update the test to either (a)
change the assertion message to "Expected 400" to match the current check, or
(b) expand the assertion to allow both status codes (e.g., assert
response.status_code in (400, 501)) and keep the existing message; ensure you
still call ErrorResponse(**response.json()) against the response object so the
error payload is validated.

ErrorResponse(**logout_response.json())
authenticated_auth_client.token = token

@allure.title("Change password with with wrong auth type")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Typo: Double "with" in title.

-    @allure.title("Change password with with wrong auth type")
+    @allure.title("Change password with wrong auth type")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@allure.title("Change password with with wrong auth type")
@allure.title("Change password with wrong auth type")
🤖 Prompt for AI Agents
In @tests/api/test_auth_password.py at line 171, Fix the typo in the test's
Allure title decorator: update the @allure.title string on the test in
tests/api/test_auth_password.py from "Change password with with wrong auth type"
to "Change password with wrong auth type" (locate the @allure.title decorator
for the test and remove the duplicate "with").

Comment on lines +246 to +255
def test_change_password_without_content_type(self, authenticated_auth_client, valid_credentials, valid_password):
headers = authenticated_auth_client.headers.copy()
headers.pop("Content-Type")
response = authenticated_auth_client.change_password(
old_password=valid_credentials["password"],
new_password=valid_password
)
assert response.status_code == 400, f"Expected 400, got {response.status_code}"
ErrorResponse(**response.json())
authenticated_auth_client.headers = headers
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Header modification is never applied to the request.

The test copies headers to a local variable, modifies the copy, but never applies it. The change_password() call still uses the client's original headers. The header restoration at line 255 also restores headers that were never changed on the client.

Proposed fix: Modify client headers directly
     def test_change_password_without_content_type(self, authenticated_auth_client, valid_credentials, valid_password):
         headers = authenticated_auth_client.headers.copy() 
-        headers.pop("Content-Type")
+        authenticated_auth_client.headers.pop("Content-Type", None)
         response = authenticated_auth_client.change_password(
             old_password=valid_credentials["password"],
             new_password=valid_password
         )       
         assert response.status_code == 400, f"Expected 400, got {response.status_code}"
         ErrorResponse(**response.json())
         authenticated_auth_client.headers = headers
🤖 Prompt for AI Agents
In @tests/api/test_auth_password.py around lines 246 - 255, The
test_change_password_without_content_type test copies headers to a local
variable and pops Content-Type from the copy but never applies it to
authenticated_auth_client, so change_password() still sends the original
headers; fix by saving the original headers (e.g., orig =
authenticated_auth_client.headers.copy()), then remove "Content-Type" directly
from authenticated_auth_client.headers before calling
authenticated_auth_client.change_password(...), assert the response, and finally
restore the original headers by assigning orig back to
authenticated_auth_client.headers.

import allure
from pydantic import ValidationError
from tests.api.schemas.auth_schemas import UserProfile, ErrorResponse
from utils.token_generator import generate_invalid_tokens
Copy link

@coderabbitai coderabbitai bot Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Import error: generate_invalid_tokens does not exist.

The import references generate_invalid_tokens, but per utils/token_generator.py (lines 8-83 in relevant snippets), the actual function is named generate_invalid_bearer_tokens. This will cause an ImportError at test collection time.

Proposed fix
-from utils.token_generator import generate_invalid_tokens
+from utils.token_generator import generate_invalid_bearer_tokens

Also update line 132:

-    @pytest.mark.parametrize("invalid_token", generate_invalid_tokens())
+    @pytest.mark.parametrize("invalid_token", generate_invalid_bearer_tokens())
🤖 Prompt for AI Agents
In @tests/api/test_auth_profile.py at line 5, The test imports a non-existent
symbol generate_invalid_tokens; replace that import with the actual function
name generate_invalid_bearer_tokens from utils.token_generator and update any
call sites in tests/api/test_auth_profile.py that reference
generate_invalid_tokens to call generate_invalid_bearer_tokens instead so the
test imports and uses the correct function name.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be implemented later.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0xc7c5, understood. Since this import error will prevent the tests from running, would you like me to create an issue to track this fix for a follow-up PR?

@chainstack chainstack deleted a comment from coderabbitai bot Jan 12, 2026
@chainstack chainstack deleted a comment from coderabbitai bot Jan 12, 2026
@chainstack chainstack deleted a comment from coderabbitai bot Jan 12, 2026
@chainstack chainstack deleted a comment from coderabbitai bot Jan 12, 2026
@chainstack chainstack deleted a comment from coderabbitai bot Jan 12, 2026
@chainstack chainstack deleted a comment from coderabbitai bot Jan 12, 2026
@chainstack chainstack deleted a comment from coderabbitai bot Jan 12, 2026
@chainstack chainstack deleted a comment from coderabbitai bot Jan 12, 2026
@chainstack chainstack deleted a comment from coderabbitai bot Jan 12, 2026
@chainstack chainstack deleted a comment from coderabbitai bot Jan 12, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🤖 Fix all issues with AI agents
In @tests/api/test_auth_login.py:
- Around line 146-152: The test test_login_missing_password is passing
password=valid_credentials["username"] to auth_client.login (testing missing
username) instead of omitting the password; update the call in
test_login_missing_password to call auth_client.login without the password
argument (or explicitly with password=None/omit) while still providing the
username from valid_credentials so the request simulates a missing password,
then keep the existing assertions (status_code == 400 and
ErrorResponse(**response.json())).
- Around line 22-33: The f-strings in stripcase_valid_credentials reuse double
quotes for both the f-string and the dict key access, which is a Python <3.12
syntax error; fix by changing the inner key access to use single quotes (e.g.,
valid_credentials['username'] and valid_credentials['password']) or assign
username/password to local variables at the top of stripcase_valid_credentials
and use those in the f-strings; update every tuple entry in
stripcase_valid_credentials to use the corrected references.
- Around line 205-213: In test_login_without_content_type the local headers copy
is modified but not applied to the client, so test_login_without_content_type
should either set auth_client.headers = headers before calling auth_client.login
or pass the modified headers into auth_client.login, then perform the request
and finally restore the original headers (e.g., reassign the saved original
headers back to auth_client.headers) to avoid side effects; update references in
the test to use auth_client.headers (or the explicit headers arg) so the
Content-Type is actually removed during the login call.
- Around line 215-224: In test_login_with_wrong_content_type the local headers
copy is modified but never applied to the request; before calling
auth_client.login in test_login_with_wrong_content_type either set
auth_client.headers = headers so the client uses the modified Content-Type or
(if auth_client.login accepts per-request headers) pass headers=headers into
auth_client.login(username, password, headers=headers); ensure you still restore
auth_client.headers to its original value afterward if you mutate the client.

In @tests/api/test_auth_password.py:
- Around line 1-6: The test file references WEAK_PASSWORD_CASES and
generate_invalid_refresh_tokens but never imports them, causing NameError; add
an import for WEAK_PASSWORD_CASES (same place as
EMPTY_STRING_CASES/NONSTRING_CASES — e.g., from tests.api.cases.test_cases
import WEAK_PASSWORD_CASES) and add an import for
generate_invalid_refresh_tokens from the module that defines token helpers
(where helper functions live in the test suite, e.g.,
tests.api.utils.token_helpers or the appropriate tests.api.cases helper module)
so both symbols are available to the tests.
- Around line 244-255: The test test_change_password_without_content_type
currently pops "Content-Type" from a local headers copy but never applies it to
authenticated_auth_client, so the request still includes the header; update the
test to remove the header directly on the client (e.g.,
authenticated_auth_client.headers.pop("Content-Type") before calling
change_password) and then restore the original headers afterward (or assign
authenticated_auth_client.headers = headers after popping) so the client
actually sends the request without Content-Type.
- Around line 257-269: The test modifies a local copy of headers but never
applies it to the client, so authenticated_auth_client.change_password still
uses the original headers; set authenticated_auth_client.headers = headers
before calling change_password (or alternatively pass headers into
change_password if that helper accepts a headers param) in
test_change_password_with_wrong_content_type so the modified Content-Type is
actually sent with the request.
- Around line 271-280: The two tests for the change-password endpoint have
mismatched expectations for the Cache-Control header
(test_change_password_check_cache expects "no-cache, no-store, must-revalidate"
while test_change_password_check_response_headers expects "no-store"); confirm
the API's actual Cache-Control value by inspecting the response from
authenticated_auth_client.change_password and then update both tests to assert
the same, correct header value (and keep the Pragma assertion as-is if it is
correct); locate the assertions in test_change_password_check_cache and
test_change_password_check_response_headers and replace the hardcoded expected
Cache-Control string in both with the verified canonical value.

In @tests/api/test_auth_profile.py:
- Around line 206-215: The test creates a modified headers dict but never
applies it to the client before calling get_profile, so the request still uses
the original headers; set authenticated_auth_client.headers = headers before
calling authenticated_auth_client.get_profile() and then restore the original
headers after the assertion (or assign headers back only for the request and
revert to previous headers) to ensure test_get_profile_with_wrong_content_type
actually sends the wrong Content-Type to get_profile.
- Around line 185-192: The test leaves authenticated_auth_client in an
inconsistent state because after calling logout() and then login(...) the
client's token and refresh_token are not updated; modify the
test_get_profile_with_revoked_access_token to capture the login response (e.g.,
resp = authenticated_auth_client.login(valid_credentials["username"],
valid_credentials["password"])), parse the returned tokens from resp.json()
(access_token / refresh_token or the actual keys your API returns) and assign
them back to authenticated_auth_client.token and
authenticated_auth_client.refresh_token (or the equivalent attributes on the
client) so the client state is fully restored after re-login.
- Around line 194-204: TestProfileHeaders is missing the same decorators used on
the other test classes; add the decorators above the class definition:
@allure.feature with the appropriate feature name, @allure.story with the
appropriate story name, and @pytest.mark.api so the class has the same metadata
as TestProfileGeneral/TestProfileAccess; place these decorators immediately
above class TestProfileHeaders to ensure the test class inherits the same
markers.

In @tests/api/test_auth_refresh.py:
- Around line 218-225: The test leaves authenticated_auth_client in an
inconsistent state because authenticated_auth_client.login() is called but its
returned tokens aren't assigned; update test_refresh_with_revoked_refresh_token
to capture the result of authenticated_auth_client.login() (or the returned
access/refresh tokens) and set them back on the authenticated_auth_client
instance (the same pattern used elsewhere where login returns tokens used to
populate authenticated_auth_client.refresh_token / access_token) so the client
is fully restored after the test; ensure you preserve the logout(),
post_refresh(...), and ErrorResponse(**response.json()) checks.
- Around line 242-251: In test_refresh_with_wrong_content_type the local headers
copy is modified but never applied to the request; set the client's headers
before calling authenticated_auth_client.post_refresh (e.g., assign
authenticated_auth_client.headers = headers) or pass headers to
authenticated_auth_client.post_refresh(headers=headers) so the modified
Content-Type is sent, and if needed restore the original headers after the
request.
🧹 Nitpick comments (5)
fixtures/api_fixtures.py (2)

38-51: Consider handling missing credentials more explicitly.

When config.user_log or config.user_pass is None, the fixture silently yields an unauthenticated client. This could lead to confusing test failures if credentials are missing from the configuration. Consider raising an explicit error or logging a warning.

Also, as flagged by static analysis, consider using a custom exception class instead of the generic Exception.

♻️ Suggested improvement
+class LoginFailedError(Exception):
+    """Raised when authentication fails during fixture setup."""
+    pass
+
+
 @pytest.fixture(scope="function")
 def authenticated_auth_client(config: Settings):
     client = AuthAPIClient(config)
-    if config.user_log and config.user_pass:
-        response = client.login(config.user_log, config.user_pass)
-        if response.status_code == 200:
-            token = response.json().get("access_token")
-            refresh_token = response.json().get("refresh_token")
-            client.token = token
-            client.refresh_token = refresh_token
-        else:
-            raise Exception("Login failed")
+    if not config.user_log or not config.user_pass:
+        raise LoginFailedError("Missing user_log or user_pass in config")
+    
+    response = client.login(config.user_log, config.user_pass)
+    if response.status_code != 200:
+        raise LoginFailedError(f"Login failed with status {response.status_code}")
+    
+    data = response.json()
+    client.token = data.get("access_token")
+    client.refresh_token = data.get("refresh_token")
     yield client
     client.close()

70-77: valid_password and invalid_password have identical implementations.

Both fixtures use faker.password(), making them semantically identical. If invalid_password should represent a non-existent user's password (for failed login tests), this is fine. However, if it should represent passwords that fail validation (e.g., too short, no special characters), the implementation should differ.

Consider renaming to clarify intent (e.g., random_password and nonexistent_user_password) or implementing actual validation-failing passwords for invalid_password.

tests/api/test_auth_profile.py (2)

141-153: Hardcoded JWT token is acceptable for testing purposes.

The static analysis flagged this as a potential secret, but this is an intentionally crafted expired test token with fake data. The TODO comment indicates plans to generate these dynamically, which would be cleaner.

Would you like me to help implement a utility function to dynamically generate expired JWT tokens for testing?


130-139: Missing token restoration after test.

Unlike test_get_profile_without_token and test_get_profile_expired_token, this test modifies the client token but doesn't restore it afterward. While authenticated_auth_client is function-scoped (so isolation should be maintained), the pattern is inconsistent with sibling tests.

♻️ Suggested fix for consistency
     @pytest.mark.parametrize("invalid_token", generate_invalid_tokens())
     def test_get_profile_invalid_token(self, authenticated_auth_client, invalid_token):
+        original_token = authenticated_auth_client.token
         authenticated_auth_client.token = invalid_token
         
         response = authenticated_auth_client.get_profile()
         
         assert response.status_code == 401, f"Expected 401, got {response.status_code}"
         ErrorResponse(**response.json())
+        authenticated_auth_client.token = original_token
tests/api/test_auth_password.py (1)

17-29: Avoid mutating fixture return values.

Line 27 mutates valid_credentials["password"] which modifies the fixture's returned dictionary. While valid_credentials is function-scoped and should return a fresh dict per test, mutating fixture values is a test smell that can lead to hard-to-debug order dependencies.

If you need to track the new password for subsequent operations, use a local variable instead.

♻️ Suggested approach
     def test_change_password_success(self, authenticated_auth_client, valid_credentials, valid_password):
         response = authenticated_auth_client.change_password(
             old_password=valid_credentials["password"],
             new_password=valid_password
         )
         
         assert response.status_code == 200, f"Expected 200, got {response.status_code}"
         try:
             change_response = ChangePasswordResponse(**response.json())
             assert change_response.message, "Response should contain success message"
-            valid_credentials["password"] = valid_password
+            # Note: Password changed to valid_password for this session
         except ValidationError as e:
             pytest.fail(f"Password change response schema validation failed: {e}")
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ebf2437 and 94ece0c.

📒 Files selected for processing (6)
  • fixtures/api_fixtures.py
  • tests/api/test_auth_audit_log.py
  • tests/api/test_auth_login.py
  • tests/api/test_auth_password.py
  • tests/api/test_auth_profile.py
  • tests/api/test_auth_refresh.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/api/test_auth_audit_log.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-12T13:38:40.404Z
Learnt from: 0xc7c5
Repo: chainstack/cp-autotests PR: 1
File: tests/api/test_auth_refresh.py:187-195
Timestamp: 2026-01-12T13:38:40.404Z
Learning: In tests/api/test_auth_refresh.py, the header manipulation pattern `headers = client.headers.copy()` followed by `client.headers["Authorization"] = ...` and later `client.headers = headers` is correct: it backs up, modifies the client's headers directly, then restores them.

Applied to files:

  • tests/api/test_auth_profile.py
  • tests/api/test_auth_refresh.py
  • tests/api/test_auth_login.py
  • tests/api/test_auth_password.py
  • fixtures/api_fixtures.py
🧬 Code graph analysis (4)
tests/api/test_auth_profile.py (3)
tests/api/schemas/auth_schemas.py (3)
  • UserProfile (6-14)
  • ErrorResponse (33-34)
  • LoginResponse (17-21)
fixtures/api_fixtures.py (2)
  • authenticated_auth_client (39-51)
  • valid_credentials (55-59)
clients/api_client.py (2)
  • get_profile (194-195)
  • login (184-185)
tests/api/test_auth_login.py (3)
tests/api/schemas/auth_schemas.py (3)
  • LoginResponse (17-21)
  • ErrorResponse (33-34)
  • UserProfile (6-14)
fixtures/api_fixtures.py (6)
  • valid_credentials (55-59)
  • auth_client (32-35)
  • invalid_username (63-64)
  • invalid_password (76-77)
  • invalid_credentials (81-85)
  • authenticated_auth_client (39-51)
clients/api_client.py (4)
  • login (184-185)
  • post (46-57)
  • send_custom_request (100-115)
  • get_profile (194-195)
tests/api/test_auth_password.py (4)
tests/api/schemas/auth_schemas.py (2)
  • ChangePasswordResponse (37-38)
  • ErrorResponse (33-34)
fixtures/api_fixtures.py (5)
  • authenticated_auth_client (39-51)
  • valid_credentials (55-59)
  • valid_password (71-72)
  • invalid_password (76-77)
  • faker (7-8)
clients/api_client.py (2)
  • change_password (197-198)
  • send_custom_request (100-115)
utils/token_generator.py (1)
  • generate_invalid_refresh_tokens (86-167)
fixtures/api_fixtures.py (2)
clients/api_client.py (2)
  • AuthAPIClient (175-205)
  • login (184-185)
config/settings.py (1)
  • Settings (6-78)
🪛 Gitleaks (8.30.0)
tests/api/test_auth_profile.py

[high] 146-146: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

🪛 Ruff (0.14.10)
tests/api/test_auth_profile.py

146-146: Possible hardcoded password assigned to: "expired_token"

(S105)

tests/api/test_auth_refresh.py

72-72: Possible hardcoded password assigned to: "expired_token"

(S105)


179-179: Possible hardcoded password assigned to: "expired_token"

(S105)

tests/api/test_auth_login.py

24-24: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


25-25: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


26-26: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


27-27: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


28-28: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


29-29: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


30-30: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


30-30: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


31-31: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


31-31: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


32-32: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


32-32: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


103-103: Undefined name valid_credentials

(F821)


112-112: Undefined name valid_credentials

(F821)

tests/api/test_auth_password.py

107-107: Undefined name WEAK_PASSWORD_CASES

(F821)


159-159: Undefined name generate_invalid_refresh_tokens

(F821)

fixtures/api_fixtures.py

49-49: Create your own exception

(TRY002)


49-49: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: guardrails/scan
🔇 Additional comments (4)
fixtures/api_fixtures.py (1)

66-68: Verify valid_username generates appropriate format.

faker.name() generates full names (e.g., "John Smith"), which may not match typical username formats. If the system expects specific username patterns, consider using faker.user_name() instead.

tests/api/test_auth_refresh.py (3)

1-7: LGTM on imports and structure.

The imports are appropriate for the test file's needs. The organization with allure decorators and pytest markers is consistent with the test suite patterns.


79-100: Verify refresh token reuse policy.

This test assumes a single refresh token can be used multiple times to generate different access tokens. If the API implements refresh token rotation (where each refresh invalidates the old refresh token), the second post_refresh call would fail.


124-142: Verify old token invalidation behavior.

This test assumes the old access token becomes immediately invalid after a refresh. Many JWT-based APIs don't implement token blacklisting—old tokens remain valid until they expire naturally. Confirm the API's token invalidation strategy matches this expectation.

Comment on lines +146 to +152
@allure.title("Login fails with missing password")
@allure.severity(allure.severity_level.NORMAL)
def test_login_missing_password(self, auth_client, valid_credentials):
response = auth_client.login(password=valid_credentials["username"])

assert response.status_code == 400, f"Expected 400, got {response.status_code}"
ErrorResponse(**response.json())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Incorrect test: passes username as password instead of omitting password.

The test title says "Login fails with missing password" but line 149 passes password=valid_credentials["username"] instead of omitting the password argument. This tests logging in with only a password (no username), not with a missing password.

🐛 Proposed fix
     @allure.title("Login fails with missing password")
     @allure.severity(allure.severity_level.NORMAL)
     def test_login_missing_password(self, auth_client, valid_credentials):
-        response = auth_client.login(password=valid_credentials["username"])
+        response = auth_client.login(username=valid_credentials["username"])
         
         assert response.status_code == 400, f"Expected 400, got {response.status_code}"
         ErrorResponse(**response.json())
🤖 Prompt for AI Agents
In @tests/api/test_auth_login.py around lines 146 - 152, The test
test_login_missing_password is passing password=valid_credentials["username"] to
auth_client.login (testing missing username) instead of omitting the password;
update the call in test_login_missing_password to call auth_client.login without
the password argument (or explicitly with password=None/omit) while still
providing the username from valid_credentials so the request simulates a missing
password, then keep the existing assertions (status_code == 400 and
ErrorResponse(**response.json())).

Comment on lines +205 to +213
@allure.title("Login without content type")
@allure.severity(allure.severity_level.NORMAL)
def test_login_without_content_type(self, auth_client, valid_credentials):
headers = auth_client.headers.copy()
headers.pop("Content-Type")
response = auth_client.login(valid_credentials["username"], valid_credentials["password"])
assert response.status_code == 400, f"Expected 400, got {response.status_code}"
ErrorResponse(**response.json())
auth_client.headers = headers
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Test doesn't remove Content-Type from client headers.

The test pops Content-Type from the local headers copy but never applies this change to auth_client.headers. The login request uses the original headers with Content-Type intact.

🐛 Proposed fix
     def test_login_without_content_type(self, auth_client, valid_credentials):
         headers = auth_client.headers.copy() 
-        headers.pop("Content-Type")
-        response = auth_client.login(valid_credentials["username"], valid_credentials["password"])       
+        auth_client.headers.pop("Content-Type")
+        response = auth_client.login(valid_credentials["username"], valid_credentials["password"])
         assert response.status_code == 400, f"Expected 400, got {response.status_code}"
         ErrorResponse(**response.json())
         auth_client.headers = headers
🤖 Prompt for AI Agents
In @tests/api/test_auth_login.py around lines 205 - 213, In
test_login_without_content_type the local headers copy is modified but not
applied to the client, so test_login_without_content_type should either set
auth_client.headers = headers before calling auth_client.login or pass the
modified headers into auth_client.login, then perform the request and finally
restore the original headers (e.g., reassign the saved original headers back to
auth_client.headers) to avoid side effects; update references in the test to use
auth_client.headers (or the explicit headers arg) so the Content-Type is
actually removed during the login call.

Comment on lines +215 to +224
@allure.title("Login with wrong content type")
@allure.severity(allure.severity_level.NORMAL)
@pytest.mark.parametrize("content_type", ["text/plain", "application/xml", "application/json; charset=utf-8"])
def test_login_with_wrong_content_type(self, auth_client, valid_credentials, content_type):
headers = auth_client.headers.copy()
headers["Content-Type"] = content_type
response = auth_client.login(valid_credentials["username"], valid_credentials["password"])
assert response.status_code == 400, f"Expected 400, got {response.status_code}"
ErrorResponse(**response.json())
auth_client.headers = headers
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Test doesn't apply modified Content-Type to client.

Same issue: the local headers copy is modified but the client's headers remain unchanged during the request.

🐛 Proposed fix
     @pytest.mark.parametrize("content_type", ["text/plain", "application/xml", "application/json; charset=utf-8"])
     def test_login_with_wrong_content_type(self, auth_client, valid_credentials, content_type):
         headers = auth_client.headers.copy() 
-        headers["Content-Type"] = content_type
-        response = auth_client.login(valid_credentials["username"], valid_credentials["password"])       
+        auth_client.headers["Content-Type"] = content_type
+        response = auth_client.login(valid_credentials["username"], valid_credentials["password"])
         assert response.status_code == 400, f"Expected 400, got {response.status_code}"
         ErrorResponse(**response.json())
         auth_client.headers = headers
🤖 Prompt for AI Agents
In @tests/api/test_auth_login.py around lines 215 - 224, In
test_login_with_wrong_content_type the local headers copy is modified but never
applied to the request; before calling auth_client.login in
test_login_with_wrong_content_type either set auth_client.headers = headers so
the client uses the modified Content-Type or (if auth_client.login accepts
per-request headers) pass headers=headers into auth_client.login(username,
password, headers=headers); ensure you still restore auth_client.headers to its
original value afterward if you mutate the client.

Comment on lines +1 to +6
import pytest
import allure
from pydantic import ValidationError
from tests.api.schemas.auth_schemas import ChangePasswordResponse, ErrorResponse
from tests.api.cases.test_cases import EMPTY_STRING_CASES, NONSTRING_CASES
import base64
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing imports for WEAK_PASSWORD_CASES and generate_invalid_refresh_tokens.

The file uses WEAK_PASSWORD_CASES (line 107) and generate_invalid_refresh_tokens (line 159) but neither is imported. This will cause NameError at runtime.

🐛 Add missing imports
 import pytest
 import allure
 from pydantic import ValidationError
 from tests.api.schemas.auth_schemas import ChangePasswordResponse, ErrorResponse
-from tests.api.cases.test_cases import EMPTY_STRING_CASES, NONSTRING_CASES
+from tests.api.cases.test_cases import EMPTY_STRING_CASES, NONSTRING_CASES, WEAK_PASSWORD_CASES
+from utils.token_generator import generate_invalid_refresh_tokens
 import base64
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import pytest
import allure
from pydantic import ValidationError
from tests.api.schemas.auth_schemas import ChangePasswordResponse, ErrorResponse
from tests.api.cases.test_cases import EMPTY_STRING_CASES, NONSTRING_CASES
import base64
import pytest
import allure
from pydantic import ValidationError
from tests.api.schemas.auth_schemas import ChangePasswordResponse, ErrorResponse
from tests.api.cases.test_cases import EMPTY_STRING_CASES, NONSTRING_CASES, WEAK_PASSWORD_CASES
from utils.token_generator import generate_invalid_refresh_tokens
import base64
🤖 Prompt for AI Agents
In @tests/api/test_auth_password.py around lines 1 - 6, The test file references
WEAK_PASSWORD_CASES and generate_invalid_refresh_tokens but never imports them,
causing NameError; add an import for WEAK_PASSWORD_CASES (same place as
EMPTY_STRING_CASES/NONSTRING_CASES — e.g., from tests.api.cases.test_cases
import WEAK_PASSWORD_CASES) and add an import for
generate_invalid_refresh_tokens from the module that defines token helpers
(where helper functions live in the test suite, e.g.,
tests.api.utils.token_helpers or the appropriate tests.api.cases helper module)
so both symbols are available to the tests.

Comment on lines +244 to +255
@allure.title("Change password without content type")
@allure.severity(allure.severity_level.NORMAL)
def test_change_password_without_content_type(self, authenticated_auth_client, valid_credentials, valid_password):
headers = authenticated_auth_client.headers.copy()
headers.pop("Content-Type")
response = authenticated_auth_client.change_password(
old_password=valid_credentials["password"],
new_password=valid_password
)
assert response.status_code == 400, f"Expected 400, got {response.status_code}"
ErrorResponse(**response.json())
authenticated_auth_client.headers = headers
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Test doesn't remove Content-Type from client headers.

Same pattern issue: pops from local headers copy but doesn't apply to authenticated_auth_client.headers.

🐛 Proposed fix
     def test_change_password_without_content_type(self, authenticated_auth_client, valid_credentials, valid_password):
         headers = authenticated_auth_client.headers.copy() 
-        headers.pop("Content-Type")
-        response = authenticated_auth_client.change_password(
+        authenticated_auth_client.headers.pop("Content-Type")
+        response = authenticated_auth_client.change_password(
             old_password=valid_credentials["password"],
             new_password=valid_password
         )       
         assert response.status_code == 400, f"Expected 400, got {response.status_code}"
         ErrorResponse(**response.json())
         authenticated_auth_client.headers = headers
🤖 Prompt for AI Agents
In @tests/api/test_auth_password.py around lines 244 - 255, The test
test_change_password_without_content_type currently pops "Content-Type" from a
local headers copy but never applies it to authenticated_auth_client, so the
request still includes the header; update the test to remove the header directly
on the client (e.g., authenticated_auth_client.headers.pop("Content-Type")
before calling change_password) and then restore the original headers afterward
(or assign authenticated_auth_client.headers = headers after popping) so the
client actually sends the request without Content-Type.

Comment on lines +185 to +192
@allure.title("Get profile with revoked access token")
@allure.severity(allure.severity_level.NORMAL)
def test_get_profile_with_revoked_access_token(self, authenticated_auth_client, valid_credentials):
authenticated_auth_client.logout()
response = authenticated_auth_client.get_profile()
assert response.status_code == 401, f"Expected 401, got {response.status_code}"
ErrorResponse(**response.json())
authenticated_auth_client.login(valid_credentials["username"], valid_credentials["password"])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Incomplete state restoration after login.

After calling logout() and login(), the client's token and refresh_token attributes are not updated with the new values from the login response. This leaves the client in an inconsistent state.

🐛 Proposed fix
     def test_get_profile_with_revoked_access_token(self, authenticated_auth_client, valid_credentials):
         authenticated_auth_client.logout()
         response = authenticated_auth_client.get_profile()       
         assert response.status_code == 401, f"Expected 401, got {response.status_code}"
         ErrorResponse(**response.json())
-        authenticated_auth_client.login(valid_credentials["username"], valid_credentials["password"])
+        login_response = authenticated_auth_client.login(valid_credentials["username"], valid_credentials["password"])
+        if login_response.status_code == 200:
+            data = login_response.json()
+            authenticated_auth_client.token = data.get("access_token")
+            authenticated_auth_client.refresh_token = data.get("refresh_token")
🤖 Prompt for AI Agents
In @tests/api/test_auth_profile.py around lines 185 - 192, The test leaves
authenticated_auth_client in an inconsistent state because after calling
logout() and then login(...) the client's token and refresh_token are not
updated; modify the test_get_profile_with_revoked_access_token to capture the
login response (e.g., resp =
authenticated_auth_client.login(valid_credentials["username"],
valid_credentials["password"])), parse the returned tokens from resp.json()
(access_token / refresh_token or the actual keys your API returns) and assign
them back to authenticated_auth_client.token and
authenticated_auth_client.refresh_token (or the equivalent attributes on the
client) so the client state is fully restored after re-login.

Comment on lines +194 to +204
class TestProfileHeaders:

@allure.title("Get profile without content type")
@allure.severity(allure.severity_level.NORMAL)
def test_get_profile_without_content_type(self, authenticated_auth_client):
headers = authenticated_auth_client.headers.copy()
authenticated_auth_client.headers.pop("Content-Type")
response = authenticated_auth_client.get_profile()
assert response.status_code == 400, f"Expected 400, got {response.status_code}"
ErrorResponse(**response.json())
authenticated_auth_client.headers = headers
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the test file
git ls-files tests/api/test_auth_profile.py

Repository: chainstack/cp-autotests

Length of output: 97


🏁 Script executed:

# Check file size
wc -l tests/api/test_auth_profile.py

Repository: chainstack/cp-autotests

Length of output: 101


🏁 Script executed:

cat -n tests/api/test_auth_profile.py

Repository: chainstack/cp-autotests

Length of output: 12757


Add missing class decorators to TestProfileHeaders.

TestProfileHeaders is missing @allure.feature, @allure.story, and @pytest.mark.api decorators that are consistently applied to the other test classes (TestProfileGeneral and TestProfileAccess).

♻️ Add missing decorators
+@allure.feature("Authentication")
+@allure.story("User Profile")
+@pytest.mark.api
 class TestProfileHeaders:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class TestProfileHeaders:
@allure.title("Get profile without content type")
@allure.severity(allure.severity_level.NORMAL)
def test_get_profile_without_content_type(self, authenticated_auth_client):
headers = authenticated_auth_client.headers.copy()
authenticated_auth_client.headers.pop("Content-Type")
response = authenticated_auth_client.get_profile()
assert response.status_code == 400, f"Expected 400, got {response.status_code}"
ErrorResponse(**response.json())
authenticated_auth_client.headers = headers
@allure.feature("Authentication")
@allure.story("User Profile")
@pytest.mark.api
class TestProfileHeaders:
@allure.title("Get profile without content type")
@allure.severity(allure.severity_level.NORMAL)
def test_get_profile_without_content_type(self, authenticated_auth_client):
headers = authenticated_auth_client.headers.copy()
authenticated_auth_client.headers.pop("Content-Type")
response = authenticated_auth_client.get_profile()
assert response.status_code == 400, f"Expected 400, got {response.status_code}"
ErrorResponse(**response.json())
authenticated_auth_client.headers = headers
🤖 Prompt for AI Agents
In @tests/api/test_auth_profile.py around lines 194 - 204, TestProfileHeaders is
missing the same decorators used on the other test classes; add the decorators
above the class definition: @allure.feature with the appropriate feature name,
@allure.story with the appropriate story name, and @pytest.mark.api so the class
has the same metadata as TestProfileGeneral/TestProfileAccess; place these
decorators immediately above class TestProfileHeaders to ensure the test class
inherits the same markers.

Comment on lines +206 to +215
@allure.title("Get profile with wrong content type")
@allure.severity(allure.severity_level.NORMAL)
@pytest.mark.parametrize("content_type", ["text/plain", "application/xml", "application/json; charset=utf-8"])
def test_get_profile_with_wrong_content_type(self, authenticated_auth_client, content_type):
headers = authenticated_auth_client.headers.copy()
headers["Content-Type"] = content_type
response = authenticated_auth_client.get_profile()
assert response.status_code == 400, f"Expected 400, got {response.status_code}"
ErrorResponse(**response.json())
authenticated_auth_client.headers = headers
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Test doesn't apply modified headers to the request.

The test modifies the local headers copy but never assigns it back to authenticated_auth_client.headers before making the request. The client uses its original headers, so this test doesn't actually verify wrong content-type behavior.

🐛 Proposed fix
     @pytest.mark.parametrize("content_type", ["text/plain", "application/xml", "application/json; charset=utf-8"])
     def test_get_profile_with_wrong_content_type(self, authenticated_auth_client, content_type):
         headers = authenticated_auth_client.headers.copy() 
-        headers["Content-Type"] = content_type
-        response = authenticated_auth_client.get_profile()       
+        authenticated_auth_client.headers["Content-Type"] = content_type
+        response = authenticated_auth_client.get_profile()
         assert response.status_code == 400, f"Expected 400, got {response.status_code}"
         ErrorResponse(**response.json())
         authenticated_auth_client.headers = headers
🤖 Prompt for AI Agents
In @tests/api/test_auth_profile.py around lines 206 - 215, The test creates a
modified headers dict but never applies it to the client before calling
get_profile, so the request still uses the original headers; set
authenticated_auth_client.headers = headers before calling
authenticated_auth_client.get_profile() and then restore the original headers
after the assertion (or assign headers back only for the request and revert to
previous headers) to ensure test_get_profile_with_wrong_content_type actually
sends the wrong Content-Type to get_profile.

Comment on lines +218 to +225
@allure.title("Refresh with revoked refresh token")
@allure.severity(allure.severity_level.NORMAL)
def test_refresh_with_revoked_refresh_token(self, authenticated_auth_client):
authenticated_auth_client.logout()
response = authenticated_auth_client.post_refresh(authenticated_auth_client.refresh_token)
assert response.status_code == 401, f"Expected 401, got {response.status_code}"
ErrorResponse(**response.json())
authenticated_auth_client.login()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Incomplete state restoration after login.

Same issue as in profile tests: login() is called but the returned tokens are not assigned to the client, leaving it in an inconsistent state.

🐛 Proposed fix
     def test_refresh_with_revoked_refresh_token(self, authenticated_auth_client):
         authenticated_auth_client.logout()
         response = authenticated_auth_client.post_refresh(authenticated_auth_client.refresh_token)       
         assert response.status_code == 401, f"Expected 401, got {response.status_code}"
         ErrorResponse(**response.json())
-        authenticated_auth_client.login()
+        login_response = authenticated_auth_client.login()
+        if login_response.status_code == 200:
+            data = login_response.json()
+            authenticated_auth_client.token = data.get("access_token")
+            authenticated_auth_client.refresh_token = data.get("refresh_token")
🤖 Prompt for AI Agents
In @tests/api/test_auth_refresh.py around lines 218 - 225, The test leaves
authenticated_auth_client in an inconsistent state because
authenticated_auth_client.login() is called but its returned tokens aren't
assigned; update test_refresh_with_revoked_refresh_token to capture the result
of authenticated_auth_client.login() (or the returned access/refresh tokens) and
set them back on the authenticated_auth_client instance (the same pattern used
elsewhere where login returns tokens used to populate
authenticated_auth_client.refresh_token / access_token) so the client is fully
restored after the test; ensure you preserve the logout(), post_refresh(...),
and ErrorResponse(**response.json()) checks.

Comment on lines +242 to +251
@allure.title("Refresh with wrong content type")
@allure.severity(allure.severity_level.NORMAL)
@pytest.mark.parametrize("content_type", ["text/plain", "application/xml", "application/json; charset=utf-8"])
def test_refresh_with_wrong_content_type(self, authenticated_auth_client, content_type):
headers = authenticated_auth_client.headers.copy()
headers["Content-Type"] = content_type
response = authenticated_auth_client.post_refresh(authenticated_auth_client.refresh_token)
assert response.status_code == 400, f"Expected 400, got {response.status_code}"
ErrorResponse(**response.json())
authenticated_auth_client.headers = headers
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Test doesn't apply modified headers to the request.

Same issue as in profile tests: the local headers copy is modified but never assigned back to the client before the request.

🐛 Proposed fix
     @pytest.mark.parametrize("content_type", ["text/plain", "application/xml", "application/json; charset=utf-8"])
     def test_refresh_with_wrong_content_type(self, authenticated_auth_client, content_type):
         headers = authenticated_auth_client.headers.copy() 
-        headers["Content-Type"] = content_type
-        response = authenticated_auth_client.post_refresh(authenticated_auth_client.refresh_token)       
+        authenticated_auth_client.headers["Content-Type"] = content_type
+        response = authenticated_auth_client.post_refresh(authenticated_auth_client.refresh_token)
         assert response.status_code == 400, f"Expected 400, got {response.status_code}"
         ErrorResponse(**response.json())
         authenticated_auth_client.headers = headers
🤖 Prompt for AI Agents
In @tests/api/test_auth_refresh.py around lines 242 - 251, In
test_refresh_with_wrong_content_type the local headers copy is modified but
never applied to the request; set the client's headers before calling
authenticated_auth_client.post_refresh (e.g., assign
authenticated_auth_client.headers = headers) or pass headers to
authenticated_auth_client.post_refresh(headers=headers) so the modified
Content-Type is sent, and if needed restore the original headers after the
request.

@chainstack chainstack deleted a comment from coderabbitai bot Jan 12, 2026
@chainstack chainstack deleted a comment from coderabbitai bot Jan 12, 2026
@chainstack chainstack deleted a comment from coderabbitai bot Jan 12, 2026
@chainstack chainstack deleted a comment from coderabbitai bot Jan 12, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @tests/api/test_auth_logout.py:
- Around line 164-172: The test calls the string attribute
authenticated_auth_client.refresh_token as a method which raises a TypeError;
replace calls to authenticated_auth_client.refresh_token(...) with the actual
client method
authenticated_auth_client.post_refresh(refresh_token=authenticated_auth_client.refresh_token)
so refresh_response is obtained correctly, and keep the logout call using
authenticated_auth_client.logout(refresh_token=...). Also update the test name
or implement concurrency (e.g., spawn threads/tasks to call logout and
post_refresh concurrently) if you truly intend to test "at the same time" race
conditions.
- Around line 144-149: The two tests (test_logout_check_response_headers and
test_logout_check_cache) assert different Cache-Control values for the same
endpoint (/v1/auth/logout) depending only on the fixture
(authenticated_auth_client vs auth_client); confirm the actual behavior in the
logout handler and make both tests assert the correct, consistent Cache-Control
header value returned by that handler. Update either
test_logout_check_response_headers or test_logout_check_cache to match the real
response (or adjust the logout implementation if it should vary by auth state),
ensuring both tests reference the same expected Cache-Control string and keep
the other header assertions (Expires, Pragma) consistent.
- Around line 229-235: The tests are inconsistent: test_logout_check_cache uses
the unauthenticated fixture auth_client but expects 200 while
test_logout_without_auth_token expects 401, and the Cache-Control expectation
differs from test_logout_check_response_headers; fix by making logout
authentication expectations consistent (either switch test_logout_check_cache to
use an authenticated fixture or change its expected status to 401 to match
test_logout_without_auth_token) and unify the Cache-Control assertion in
test_logout_check_cache with the actual endpoint behavior (update the expected
value to match the implementation or assert a subset/contains check like
"no-store" if multiple directives are returned) so both tests reference the same
header semantics.
🧹 Nitpick comments (5)
tests/api/test_auth_logout.py (5)

1-7: Remove unused imports.

LoginResponse and UserProfile are imported but never used in this file.

Suggested fix
 import pytest
 import allure
 from pydantic import ValidationError
-from tests.api.schemas.auth_schemas import LogoutResponse, ErrorResponse, LoginResponse, UserProfile
+from tests.api.schemas.auth_schemas import LogoutResponse, ErrorResponse
 from tests.api.cases.test_cases import EMPTY_STRING_CASES
 from utils.token_generator import generate_invalid_refresh_tokens, generate_expired_token
 import base64

29-84: Missing class-level allure decorators for consistency.

TestLogoutRefreshToken lacks the @allure.feature, @allure.story, and @pytest.mark.api decorators that are applied to other test classes in this file (e.g., TestLogoutGeneral, TestLogoutAcess).

Suggested fix
+@allure.feature("Authentication")
+@allure.story("Logout")
+@pytest.mark.api
 class TestLogoutRefreshToken:

89-89: Typo in class name: "Acess" should be "Access".

Suggested fix
-class TestLogoutAcess:
+class TestLogoutAccess:

112-112: Typo in allure title: duplicate "with".

Suggested fix
-    @allure.title("Logout with with wrong auth type")
+    @allure.title("Logout with wrong auth type")

151-162: Missing class-level allure decorators and test is sequential, not concurrent.

  1. TestLogoutRaceConditions lacks class-level decorators for consistency with other classes.
  2. The test test_logout_multiple_times executes requests sequentially. While this tests idempotency behavior correctly, the class name "RaceConditions" may be misleading since no actual concurrent execution occurs.
Suggested fix for decorators
+@allure.feature("Authentication")
+@allure.story("Logout")
+@pytest.mark.api
 class TestLogoutRaceConditions:
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 94ece0c and 8b1922d.

📒 Files selected for processing (1)
  • tests/api/test_auth_logout.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-12T13:38:40.404Z
Learnt from: 0xc7c5
Repo: chainstack/cp-autotests PR: 1
File: tests/api/test_auth_refresh.py:187-195
Timestamp: 2026-01-12T13:38:40.404Z
Learning: In tests/api/test_auth_refresh.py, the header manipulation pattern `headers = client.headers.copy()` followed by `client.headers["Authorization"] = ...` and later `client.headers = headers` is correct: it backs up, modifies the client's headers directly, then restores them.

Applied to files:

  • tests/api/test_auth_logout.py
🧬 Code graph analysis (1)
tests/api/test_auth_logout.py (4)
tests/api/schemas/auth_schemas.py (2)
  • LogoutResponse (29-30)
  • ErrorResponse (33-34)
utils/token_generator.py (1)
  • generate_invalid_refresh_tokens (86-167)
fixtures/api_fixtures.py (2)
  • authenticated_auth_client (39-51)
  • auth_client (32-35)
clients/api_client.py (5)
  • logout (190-192)
  • get_profile (194-195)
  • post_refresh (187-188)
  • send_custom_request (100-115)
  • post (46-57)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: guardrails/scan
🔇 Additional comments (1)
tests/api/test_auth_logout.py (1)

220-220: Verify if application/json; charset=utf-8 should be rejected.

application/json; charset=utf-8 is a valid and common Content-Type header. Many APIs accept it as equivalent to application/json. Consider verifying whether the API intentionally rejects charset parameters or if this test case expectation is incorrect.

Comment on lines +144 to +149
def test_logout_check_response_headers(self, authenticated_auth_client):
response = authenticated_auth_client.logout()
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
assert response.headers["Cache-Control"] == "no-store", "Cache-Control should be no-store"
assert response.headers["Expires"] == "0", "Expires should be 0"
assert response.headers["Pragma"] == "no-cache", "Pragma should be no-cache"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, locate the test file
find . -name "test_auth_logout.py" -type f

Repository: chainstack/cp-autotests

Length of output: 98


🏁 Script executed:

# Read the test file around the mentioned lines
wc -l tests/api/test_auth_logout.py

Repository: chainstack/cp-autotests

Length of output: 100


🏁 Script executed:

# Read the test around lines 144-149
sed -n '140,150p' tests/api/test_auth_logout.py

Repository: chainstack/cp-autotests

Length of output: 707


🏁 Script executed:

# Read the test around line 234
sed -n '230,240p' tests/api/test_auth_logout.py

Repository: chainstack/cp-autotests

Length of output: 571


🏁 Script executed:

# Get a broader view of the test structure around line 234
sed -n '220,250p' tests/api/test_auth_logout.py

Repository: chainstack/cp-autotests

Length of output: 1124


🏁 Script executed:

# Find the logout endpoint implementation
find . -type f -name "*.py" | xargs grep -l "def logout" | head -5

Repository: chainstack/cp-autotests

Length of output: 90


🏁 Script executed:

# Inspect the api_client implementation
sed -n '1,50p' ./clients/api_client.py | head -30

Repository: chainstack/cp-autotests

Length of output: 1014


🏁 Script executed:

# Search for the logout method implementation
grep -n "def logout" ./clients/api_client.py -A 10

Repository: chainstack/cp-autotests

Length of output: 731


🏁 Script executed:

# Also check if there's any middleware or endpoint decorator that sets headers based on auth
find . -type f -name "*.py" -path "*/api/*" | xargs grep -l "logout" | grep -v test | head -10

Repository: chainstack/cp-autotests

Length of output: 49


🏁 Script executed:

# Search for fixture definitions
find . -type f -name "conftest.py" | xargs grep -l "authenticated_auth_client\|auth_client"

Repository: chainstack/cp-autotests

Length of output: 51


🏁 Script executed:

# Check the fixture definitions
grep -n "authenticated_auth_client\|auth_client" tests/ -r --include="*.py" | grep "def\|@fixture" | head -20

Repository: chainstack/cp-autotests

Length of output: 2571


🏁 Script executed:

# Find conftest files
find . -name "conftest.py" -type f

Repository: chainstack/cp-autotests

Length of output: 80


🏁 Script executed:

# Read the conftest to see fixture definitions
cat tests/conftest.py | head -100

Repository: chainstack/cp-autotests

Length of output: 116


🏁 Script executed:

# Also look for authenticated_auth_client and auth_client fixtures specifically
grep -n "authenticated_auth_client\|def auth_client" tests/conftest.py -A 5

Repository: chainstack/cp-autotests

Length of output: 117


🏁 Script executed:

# Search for logout endpoint in backend code
find . -type f \( -name "*.py" -o -name "*.js" -o -name "*.ts" \) ! -path "*/tests/*" ! -path "*/node_modules/*" | xargs grep -l "logout" 2>/dev/null | head -10

Repository: chainstack/cp-autotests

Length of output: 90


🏁 Script executed:

# Read the root conftest.py
cat ./conftest.py

Repository: chainstack/cp-autotests

Length of output: 629


🏁 Script executed:

# Also check if there's any documentation or backend code showing expected headers
find . -type f \( -name "*.md" -o -name "*.yaml" -o -name "*.yml" \) | xargs grep -l "Cache-Control\|logout" 2>/dev/null | head -5

Repository: chainstack/cp-autotests

Length of output: 93


🏁 Script executed:

# Search for where authenticated_auth_client and auth_client are defined
grep -r "def authenticated_auth_client\|def auth_client" . --include="*.py"

Repository: chainstack/cp-autotests

Length of output: 204


🏁 Script executed:

# Check the test_auth_logout.py file more comprehensively to see fixture usage
head -50 tests/api/test_auth_logout.py

Repository: chainstack/cp-autotests

Length of output: 2410


🏁 Script executed:

# Check the openapi.yaml for logout endpoint documentation
grep -A 20 "logout" ./openapi.yaml

Repository: chainstack/cp-autotests

Length of output: 1386


🏁 Script executed:

# Read the api_fixtures.py to see the difference between auth_client and authenticated_auth_client
cat ./fixtures/api_fixtures.py

Repository: chainstack/cp-autotests

Length of output: 2156


Cache-Control header values are inconsistent across logout endpoint tests.

test_logout_check_response_headers expects "no-store", while test_logout_check_cache expects "no-cache, no-store, must-revalidate" for the same endpoint. Both tests call /v1/auth/logout and expect a 200 response, but with different authentication states (authenticated_auth_client vs auth_client).

Verify whether the endpoint intentionally returns different Cache-Control headers based on authentication state, or if one test's expectation is incorrect. Align both tests to match the actual endpoint behavior.

🤖 Prompt for AI Agents
In @tests/api/test_auth_logout.py around lines 144 - 149, The two tests
(test_logout_check_response_headers and test_logout_check_cache) assert
different Cache-Control values for the same endpoint (/v1/auth/logout) depending
only on the fixture (authenticated_auth_client vs auth_client); confirm the
actual behavior in the logout handler and make both tests assert the correct,
consistent Cache-Control header value returned by that handler. Update either
test_logout_check_response_headers or test_logout_check_cache to match the real
response (or adjust the logout implementation if it should vary by auth state),
ensuring both tests reference the same expected Cache-Control string and keep
the other header assertions (Expires, Pragma) consistent.

Comment on lines +164 to +172
@allure.title("Logout and refresh token at the same time")
@allure.severity(allure.severity_level.NORMAL)
def test_logout_and_refresh_token_at_the_same_time(self, authenticated_auth_client):
logout_response = authenticated_auth_client.logout(refresh_token=authenticated_auth_client.refresh_token)
refresh_response = authenticated_auth_client.refresh_token(refresh_token=authenticated_auth_client.refresh_token)
assert logout_response.status_code == 200, f"Expected 200, got {logout_response.status_code}"
LogoutResponse(**logout_response.json())
assert refresh_response.status_code == 401, f"Expected 401, got {refresh_response.status_code}"
ErrorResponse(**refresh_response.json())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical bug: calling string attribute as a method.

Line 168 calls authenticated_auth_client.refresh_token(...), but refresh_token is a string attribute, not a method. This will raise TypeError: 'str' object is not callable. Based on the API client code, the correct method is post_refresh().

Additionally, this test claims to execute "at the same time" but runs sequentially, so it doesn't actually test race conditions.

Proposed fix
     @allure.title("Logout and refresh token at the same time")
     @allure.severity(allure.severity_level.NORMAL)
     def test_logout_and_refresh_token_at_the_same_time(self, authenticated_auth_client):
         logout_response = authenticated_auth_client.logout(refresh_token=authenticated_auth_client.refresh_token)
-        refresh_response = authenticated_auth_client.refresh_token(refresh_token=authenticated_auth_client.refresh_token)
+        refresh_response = authenticated_auth_client.post_refresh(authenticated_auth_client.refresh_token)
         assert logout_response.status_code == 200, f"Expected 200, got {logout_response.status_code}"
         LogoutResponse(**logout_response.json())
         assert refresh_response.status_code == 401, f"Expected 401, got {refresh_response.status_code}"
         ErrorResponse(**refresh_response.json())
🤖 Prompt for AI Agents
In @tests/api/test_auth_logout.py around lines 164 - 172, The test calls the
string attribute authenticated_auth_client.refresh_token as a method which
raises a TypeError; replace calls to
authenticated_auth_client.refresh_token(...) with the actual client method
authenticated_auth_client.post_refresh(refresh_token=authenticated_auth_client.refresh_token)
so refresh_response is obtained correctly, and keep the logout call using
authenticated_auth_client.logout(refresh_token=...). Also update the test name
or implement concurrency (e.g., spawn threads/tasks to call logout and
post_refresh concurrently) if you truly intend to test "at the same time" race
conditions.

Comment on lines +229 to +235
@allure.title("Logout check cache")
@allure.severity(allure.severity_level.NORMAL)
def test_logout_check_cache(self, auth_client):
response = auth_client.logout()
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
assert response.headers["Cache-Control"] == "no-cache, no-store, must-revalidate", "Cache-Control header should be set to no-cache, no-store, must-revalidate"
assert response.headers["Pragma"] == "no-cache", "Pragma header should be set to no-cache"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find auth_client fixture definition
rg -n "def auth_client" --type py -A 15 tests/

Repository: chainstack/cp-autotests

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Check test_logout_without_auth_token and test_logout_check_response_headers
rg -n "test_logout_without_auth_token|test_logout_check_response_headers" --type py -A 8 tests/

Repository: chainstack/cp-autotests

Length of output: 1685


🏁 Script executed:

#!/bin/bash
# Get context on the test file structure
head -50 tests/api/test_auth_logout.py

Repository: chainstack/cp-autotests

Length of output: 2410


🏁 Script executed:

#!/bin/bash
# Get the exact test at lines 229-235
sed -n '229,235p' tests/api/test_auth_logout.py

Repository: chainstack/cp-autotests

Length of output: 610


🏁 Script executed:

#!/bin/bash
# Find conftest.py files to understand fixture definitions
fd conftest.py tests/ -x cat {}

Repository: chainstack/cp-autotests

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Find authenticated_auth_client fixture
rg -n "def authenticated_auth_client" --type py -A 10 tests/

Repository: chainstack/cp-autotests

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Find all conftest files
find tests/ -name "conftest.py" -type f

Repository: chainstack/cp-autotests

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Search for auth_client fixture definition
rg -n "auth_client" --type py -B 2 -A 10 tests/ | head -100

Repository: chainstack/cp-autotests

Length of output: 8347


🏁 Script executed:

#!/bin/bash
# Check for fixture decorators in the test file itself
rg -n "@pytest.fixture" --type py -A 3 tests/api/test_auth_logout.py

Repository: chainstack/cp-autotests

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Find all conftest files in the repo
find . -name conftest.py -type f 2>/dev/null

Repository: chainstack/cp-autotests

Length of output: 80


🏁 Script executed:

#!/bin/bash
# Search for auth_client fixture at root level
rg -n "def auth_client|auth_client.*=" --type py -B 2 -A 8 | head -80

Repository: chainstack/cp-autotests

Length of output: 7615


🏁 Script executed:

#!/bin/bash
# Look at broader pytest fixture definitions
rg -n "@pytest.fixture.*auth" --type py -A 15 | head -150

Repository: chainstack/cp-autotests

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Read the conftest.py file to find fixtures
cat conftest.py

Repository: chainstack/cp-autotests

Length of output: 629


🏁 Script executed:

#!/bin/bash
# Check for fixture definitions in conftest.py
rg -n "def.*auth.*client|@pytest.fixture" conftest.py -A 10

Repository: chainstack/cp-autotests

Length of output: 345


🏁 Script executed:

#!/bin/bash
# Find conftest.py in tests/api directory
cat tests/api/conftest.py 2>/dev/null || echo "File not found"

Repository: chainstack/cp-autotests

Length of output: 81


🏁 Script executed:

#!/bin/bash
# Search for conftest files in subdirectories
find tests -name "conftest.py" -type f -exec cat {} \;

Repository: chainstack/cp-autotests

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Search for auth_client and authenticated_auth_client fixture definitions broadly
rg -n "@pytest.fixture" --type py -A 10 | grep -A 10 "auth_client"

Repository: chainstack/cp-autotests

Length of output: 1140


🏁 Script executed:

#!/bin/bash
# Search for fixture definitions in all Python files
fd -e py -x rg -l "def auth_client|def authenticated_auth_client" {}

Repository: chainstack/cp-autotests

Length of output: 93


🏁 Script executed:

#!/bin/bash
# Look in tests directory structure
ls -la tests/

Repository: chainstack/cp-autotests

Length of output: 394


🏁 Script executed:

#!/bin/bash
# Read the full api_fixtures.py to understand fixture definitions
cat fixtures/api_fixtures.py

Repository: chainstack/cp-autotests

Length of output: 2156


🏁 Script executed:

#!/bin/bash
# Find logout endpoint implementation
find . -name "*.py" -path "*/routes/*" -o -path "*/endpoints/*" | head -20

Repository: chainstack/cp-autotests

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Search for logout route/endpoint implementation
rg -n "def logout" --type py -B 2 -A 15 | grep -v test | head -100

Repository: chainstack/cp-autotests

Length of output: 1548


🏁 Script executed:

#!/bin/bash
# Check the auth client implementation to understand logout method
rg -n "def logout|class AuthAPIClient" --type py -A 20 clients/

Repository: chainstack/cp-autotests

Length of output: 2451


Verify authentication requirement and Cache-Control header inconsistencies for logout endpoint.

  1. The test uses auth_client (unauthenticated fixture) expecting 200, but test_logout_without_auth_token expects 401 for unauthenticated logout requests. Either the logout endpoint should reject unauthenticated requests consistently, or the test expectation needs clarification.

  2. Cache-Control header value differs between tests: test_logout_check_response_headers expects "no-store" (line 147), while this test expects "no-cache, no-store, must-revalidate" (line 235). Verify which value is correct per the actual endpoint implementation.

🤖 Prompt for AI Agents
In @tests/api/test_auth_logout.py around lines 229 - 235, The tests are
inconsistent: test_logout_check_cache uses the unauthenticated fixture
auth_client but expects 200 while test_logout_without_auth_token expects 401,
and the Cache-Control expectation differs from
test_logout_check_response_headers; fix by making logout authentication
expectations consistent (either switch test_logout_check_cache to use an
authenticated fixture or change its expected status to 401 to match
test_logout_without_auth_token) and unify the Cache-Control assertion in
test_logout_check_cache with the actual endpoint behavior (update the expected
value to match the implementation or assert a subset/contains check like
"no-store" if multiple directives are returned) so both tests reference the same
header semantics.

@0xc7c5 0xc7c5 force-pushed the feature/auth-tests-api branch from 8b1922d to 3a5d867 Compare January 19, 2026 13:09
@0xc7c5 0xc7c5 merged commit cc0cc5c into master Jan 19, 2026
1 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants