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>
262 lines
11 KiB
Markdown
262 lines
11 KiB
Markdown
# 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 existing `db` fixture)
|
||
- `auth_manager` → Use the real AuthManager but with test users/tokens (existing `auth_fixtures.py`)
|
||
- `request` → Mock FastAPI Request with `request.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 user
|
||
- `test_user` — regular user
|
||
|
||
**New fixtures to add in the test file:**
|
||
|
||
- `mock_request` — Mock Request with configurable state and url.path
|
||
- `test_customer` — Customer model for customer auth tests
|
||
- `test_merchant` — Merchant model for merchant auth tests
|
||
- `test_store` — Store with platform association
|
||
- `admin_token` — JWT token for admin user
|
||
- `store_token` — JWT token with store context claims
|
||
- `customer_token` — Customer JWT token
|
||
- `merchant_token` — JWT token for merchant owner
|
||
|
||
## Execution Order
|
||
|
||
1. **Phase 1 first** — helper functions have no dependencies on other deps.py functions
|
||
2. **Phase 2–5 next** — auth functions build on helpers
|
||
3. **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
|
||
|
||
```bash
|
||
# 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.py` have no tests
|