Cover all core authentication paths: helpers (_get_token_from_request, _validate_user_token, _get_user_model, _validate_customer_token), admin/store/merchant/customer auth (cookie + header + API variants), optional auth, store permission factories, and store ownership checks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
11 KiB
Test Plan: app/api/deps.py — Authentication Dependencies
Date: 2026-02-19
Status: Planned
Priority: P0 — Security-critical, zero test coverage
File under test: app/api/deps.py (1,668 lines, 31 functions)
Why This File Is Critical
deps.py is the single entry point for all authentication and authorization in the application. Every protected route depends on it. It enforces:
- Role isolation (admins can't access store routes, stores can't access admin routes)
- Cookie path restrictions (prevent cross-context token leakage)
- Token priority (Authorization header > cookie)
- Platform access control for platform admins
- Store permission checks (owner, team member, specific permissions)
- Merchant ownership verification
- Customer token validation with store-matching
- Module and menu-based access control
A bug here is a security vulnerability. Zero test coverage is unacceptable.
Current State
- Existing test coverage: None for deps.py directly
- Related tests:
tests/integration/security/test_authentication.py(3 tests — basic endpoint protection only) - Related tests:
tests/unit/models/schema/test_auth.py(tests UserContext schema)
Test Structure
Tests will live at: tests/unit/api/test_deps.py
This follows the project convention of matching source layout (app/api/deps.py → tests/unit/api/test_deps.py).
Functions To Test (31 total)
Phase 1: Helper Functions (4 functions)
These are pure/near-pure functions — easiest to test, highest value as foundation.
| Function | Lines | What it does | Test count |
|---|---|---|---|
_get_token_from_request() |
76–105 | Extract token from header or cookie, header takes priority | 4 |
_validate_user_token() |
108–123 | Validate JWT, return User model | 3 |
_get_user_model() |
126–155 | Load User from DB by UserContext.id, copy token attrs | 3 |
_validate_customer_token() |
1026–1114 | Decode customer JWT, verify type/expiry/store match | 7 |
Tests for _get_token_from_request:
- Returns (token, "header") when Authorization header present
- Returns (token, "cookie") when only cookie present
- Header takes priority over cookie when both present
- Returns (None, None) when neither present
Tests for _validate_user_token:
- Returns User for valid token
- Raises InvalidTokenException for invalid/expired token
- Raises InvalidTokenException for token with non-existent user
Tests for _get_user_model:
- Returns User model with token attributes copied
- Raises InvalidTokenException when user not in DB
- Copies token_store_id, token_store_code, token_store_role from context
Tests for _validate_customer_token:
- Returns CustomerContext for valid customer token
- Rejects token with wrong type (not "customer")
- Rejects token with missing "sub" claim
- Rejects expired token
- Rejects token for non-existent customer
- Rejects token for inactive customer
- Rejects token with store_id mismatch (cross-store attack)
Phase 2: Admin Authentication (6 functions)
| Function | Lines | What it does | Test count |
|---|---|---|---|
get_current_admin_from_cookie_or_header() |
163–209 | Admin auth via cookie or header | 4 |
get_current_admin_api() |
212–242 | Admin auth via header only (CSRF-safe) | 3 |
get_current_super_admin() |
250–283 | Require super admin role | 3 |
get_current_super_admin_api() |
286–312 | Super admin, header only | 2 |
require_platform_access() |
315–357 | Factory: platform-specific admin access | 4 |
get_admin_with_platform_context() |
360–430 | Admin with platform from JWT | 4 |
Key tests:
- Valid admin token accepted (cookie and header)
- Non-admin role rejected with AdminRequiredException
- No token raises InvalidTokenException
- Super admin check: platform admin rejected
- Platform access: super admin bypasses, platform admin checked against accessible_platform_ids
- Platform context: platform_id extracted from token, stored in request.state
Phase 3: Store Authentication (2 functions)
| Function | Lines | What it does | Test count |
|---|---|---|---|
get_current_store_from_cookie_or_header() |
665–720 | Store auth via cookie or header | 5 |
get_current_store_api() |
723–780 | Store API auth, validates store context | 5 |
Key tests:
- Valid store token accepted
- Admin blocked from store routes (critical security boundary)
- Customer blocked from store routes
- No token raises InvalidTokenException
- Store API requires token_store_id claim
- Store API verifies user still member of store (revocation check)
Phase 4: Merchant Authentication (5 functions)
| Function | Lines | What it does | Test count |
|---|---|---|---|
get_current_merchant_from_cookie_or_header() |
788–848 | Merchant auth, verifies ownership | 4 |
get_current_merchant_api() |
851–896 | Merchant API auth | 3 |
get_current_merchant_optional() |
899–940 | Returns None if not authenticated | 3 |
get_merchant_for_current_user() |
943–979 | Load Merchant object for API user | 3 |
get_merchant_for_current_user_page() |
982–1018 | Load Merchant object for page user | 2 |
Key tests:
- Valid merchant owner accepted
- User without active merchants rejected
- Optional variant returns None on failure (no exception)
- Merchant object stored in request.state
Phase 5: Customer Authentication (2 functions)
| Function | Lines | What it does | Test count |
|---|---|---|---|
get_current_customer_from_cookie_or_header() |
1117–1156 | Customer auth via cookie or header | 3 |
get_current_customer_api() |
1159–1185 | Customer API auth | 2 |
Key tests:
- Valid customer token accepted
- No token raises InvalidTokenException
- API variant requires Authorization header
Phase 6: Access Control (6 functions)
| Function | Lines | What it does | Test count |
|---|---|---|---|
require_module_access() |
438–538 | Factory: module enablement check | 4 |
require_menu_access() |
546–657 | Factory: menu visibility check | 4 |
require_store_permission() |
1278–1324 | Factory: specific permission check | 4 |
require_store_owner() |
1327–1369 | Require store owner role | 3 |
require_any_store_permission() |
1372–1425 | Factory: ANY of N permissions | 3 |
require_all_store_permissions() |
1428–1483 | Factory: ALL of N permissions | 3 |
Key tests:
- Module disabled → InsufficientPermissionsException
- Super admin bypasses module check
- Store owner has all permissions
- Team member checked against specific permissions
- Missing permission raises InsufficientStorePermissionsException
Phase 7: Optional Auth & Utilities (4 functions)
| Function | Lines | What it does | Test count |
|---|---|---|---|
get_current_admin_optional() |
1535–1579 | Returns None on failure | 3 |
get_current_store_optional() |
1582–1626 | Returns None on failure | 3 |
get_current_customer_optional() |
1629–1667 | Returns None on failure | 3 |
get_user_permissions() |
1486–1527 | List all permissions for user in store | 3 |
get_user_store() |
1225–1270 | Verify store ownership/membership | 3 |
Key tests:
- Optional variants return None (not raise) when token invalid
- Optional variants return context when token valid
- get_user_permissions returns all permissions for owner, specific for team member
Total Test Count
| Phase | Functions | Tests |
|---|---|---|
| 1. Helpers | 4 | 17 |
| 2. Admin auth | 6 | 20 |
| 3. Store auth | 2 | 10 |
| 4. Merchant auth | 5 | 15 |
| 5. Customer auth | 2 | 5 |
| 6. Access control | 6 | 21 |
| 7. Optional & utils | 5 | 15 |
| Total | 30 | ~103 |
Test Approach
Unit vs Integration
These will be unit tests with mocked dependencies:
db→ SQLAlchemy session with test data (using existingdbfixture)auth_manager→ Use the real AuthManager but with test users/tokens (existingauth_fixtures.py)request→ Mock FastAPI Request withrequest.state,request.url.path- FastAPI
Depends()→ Call functions directly, passing dependencies explicitly
This avoids the overhead of full HTTP request cycles and tests the logic in isolation.
Fixtures Needed
Most already exist in tests/fixtures/auth_fixtures.py:
test_admin— admin user (is_super_admin=True)test_platform_admin— platform admin (is_super_admin=False)test_store_user— store-role usertest_user— regular user
New fixtures to add in the test file:
mock_request— Mock Request with configurable state and url.pathtest_customer— Customer model for customer auth teststest_merchant— Merchant model for merchant auth teststest_store— Store with platform associationadmin_token— JWT token for admin userstore_token— JWT token with store context claimscustomer_token— Customer JWT tokenmerchant_token— JWT token for merchant owner
Execution Order
- Phase 1 first — helper functions have no dependencies on other deps.py functions
- Phase 2–5 next — auth functions build on helpers
- Phase 6–7 last — access control builds on auth functions
Phases 2–5 can be done in parallel. Phase 6 depends on 2–3 being done.
Verification
# Run just the deps tests
python -m pytest tests/unit/api/test_deps.py -v --timeout=60
# Run with coverage
python -m pytest tests/unit/api/test_deps.py -v --cov=app.api.deps --cov-report=term-missing
Corrected Coverage Summary
The earlier 360 analysis incorrectly reported module service coverage at 10%. The actual numbers (after finding tests in app/modules/*/tests/):
| Module | Services | Unit Tests | Integration Tests | Coverage |
|---|---|---|---|---|
| billing | 7 | 8 | 4 | 100%+ |
| catalog | 4 | 4 | 0 | 100% |
| checkout | 1 | 1 | 0 | 100% |
| cms | 4 | 4 | 0 | 100% |
| core | 6 | 5 | 0 | 83% |
| customers | 3 | 3 | 0 | 100% |
| dev_tools | 2 | 2 | 0 | 100% |
| inventory | 3 | 3 | 0 | 100% |
| loyalty | 8 | 8 | 1 | 100% |
| marketplace | 5 | 8 | 0 | 100%+ |
| messaging | 6 | 5 | 0 | 83% |
| monitoring | 6 | 6 | 0 | 100% |
| orders | 6 | 5 | 0 | 83% |
| payments | 2 | 2 | 0 | 100% |
| tenancy | 11 | 17 | 6 | 100%+ |
| cart | 1 | 1 | 0 | 100% |
| analytics | 1 | 0 | 0 | 0% |
Remaining gaps (non-service):
- Models: 12 modules have no model tests
- Schemas: 12 modules have no schema tests
- Routes: 14 modules have no route/integration tests
- Tasks: 0 modules have task tests (5 modules have tasks)
- Framework:
app/api/deps.py,middleware/auth.py,app/exceptions/,app/handlers/stripe_webhook.pyhave no tests