test: add 73 unit tests for app/api/deps.py auth dependencies

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>
This commit is contained in:
2026-02-19 17:38:31 +01:00
parent e5dbd7ef1a
commit 93731b7173
3 changed files with 1563 additions and 0 deletions

View File

@@ -0,0 +1,261 @@
# 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()` | 76105 | Extract token from header or cookie, header takes priority | 4 |
| `_validate_user_token()` | 108123 | Validate JWT, return User model | 3 |
| `_get_user_model()` | 126155 | Load User from DB by UserContext.id, copy token attrs | 3 |
| `_validate_customer_token()` | 10261114 | 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()` | 163209 | Admin auth via cookie or header | 4 |
| `get_current_admin_api()` | 212242 | Admin auth via header only (CSRF-safe) | 3 |
| `get_current_super_admin()` | 250283 | Require super admin role | 3 |
| `get_current_super_admin_api()` | 286312 | Super admin, header only | 2 |
| `require_platform_access()` | 315357 | Factory: platform-specific admin access | 4 |
| `get_admin_with_platform_context()` | 360430 | 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()` | 665720 | Store auth via cookie or header | 5 |
| `get_current_store_api()` | 723780 | 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()` | 788848 | Merchant auth, verifies ownership | 4 |
| `get_current_merchant_api()` | 851896 | Merchant API auth | 3 |
| `get_current_merchant_optional()` | 899940 | Returns None if not authenticated | 3 |
| `get_merchant_for_current_user()` | 943979 | Load Merchant object for API user | 3 |
| `get_merchant_for_current_user_page()` | 9821018 | 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()` | 11171156 | Customer auth via cookie or header | 3 |
| `get_current_customer_api()` | 11591185 | 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()` | 438538 | Factory: module enablement check | 4 |
| `require_menu_access()` | 546657 | Factory: menu visibility check | 4 |
| `require_store_permission()` | 12781324 | Factory: specific permission check | 4 |
| `require_store_owner()` | 13271369 | Require store owner role | 3 |
| `require_any_store_permission()` | 13721425 | Factory: ANY of N permissions | 3 |
| `require_all_store_permissions()` | 14281483 | 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()` | 15351579 | Returns None on failure | 3 |
| `get_current_store_optional()` | 15821626 | Returns None on failure | 3 |
| `get_current_customer_optional()` | 16291667 | Returns None on failure | 3 |
| `get_user_permissions()` | 14861527 | List all permissions for user in store | 3 |
| `get_user_store()` | 12251270 | 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 25 next** — auth functions build on helpers
3. **Phase 67 last** — access control builds on auth functions
Phases 25 can be done in parallel. Phase 6 depends on 23 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

View File

1302
tests/unit/api/test_deps.py Normal file

File diff suppressed because it is too large Load Diff