439 lines
11 KiB
Markdown
439 lines
11 KiB
Markdown
|
|
# Middleware Tests Documentation
|
|||
|
|
|
|||
|
|
**Date:** 2025-11-10
|
|||
|
|
**Status:** ✅ Completed
|
|||
|
|
**Test File:** `apps/api/src/__tests__/middlewares/middlewares.test.ts`
|
|||
|
|
|
|||
|
|
## Overview
|
|||
|
|
|
|||
|
|
Comprehensive test suite for all API middlewares, with special focus on authentication and authorization middlewares. The tests verify that each middleware correctly injects dependencies, validates inputs, and handles error cases.
|
|||
|
|
|
|||
|
|
## Test Results
|
|||
|
|
|
|||
|
|
### ✅ All Tests Passing
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
ℹ tests 116
|
|||
|
|
ℹ pass 116
|
|||
|
|
ℹ fail 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**22 new middleware tests added** (94 tests → 116 tests)
|
|||
|
|
|
|||
|
|
## Middleware Test Coverage
|
|||
|
|
|
|||
|
|
### 1. **Supabase Middleware** (1 test)
|
|||
|
|
Tests that the Supabase client is correctly injected into the request context.
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
✓ should inject supabase client into context
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**What it validates:**
|
|||
|
|
- Supabase client instance is available in context
|
|||
|
|
- Client is properly initialized
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 2. **Auth Middleware** (4 tests)
|
|||
|
|
Tests Bearer token authentication with various scenarios.
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
✓ should reject requests without authorization header
|
|||
|
|
✓ should reject requests with invalid Bearer prefix
|
|||
|
|
✓ should reject requests with invalid token
|
|||
|
|
✓ should reject requests with empty Bearer token
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**What it validates:**
|
|||
|
|
- Missing Authorization header → 401 with appropriate error
|
|||
|
|
- Invalid format (not "Bearer ") → 401
|
|||
|
|
- Invalid/expired token → 401
|
|||
|
|
- Empty token → 401
|
|||
|
|
- Proper error messages returned
|
|||
|
|
|
|||
|
|
**Key behaviors:**
|
|||
|
|
- Requires `Authorization: Bearer <token>` header
|
|||
|
|
- Validates token with Supabase auth
|
|||
|
|
- Returns 401 for any auth failure
|
|||
|
|
- Sets `user` in context on success
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 3. **Maybe Authenticated Middleware** (3 tests)
|
|||
|
|
Tests optional authentication - allows requests with or without valid tokens.
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
✓ should allow requests without authorization header
|
|||
|
|
✓ should set user to null with invalid token
|
|||
|
|
✓ should ignore malformed authorization header
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**What it validates:**
|
|||
|
|
- Requests without auth header pass through
|
|||
|
|
- User is set to `null` when no valid token
|
|||
|
|
- Invalid tokens don't block the request
|
|||
|
|
- Malformed headers are ignored gracefully
|
|||
|
|
|
|||
|
|
**Key behaviors:**
|
|||
|
|
- Never blocks requests
|
|||
|
|
- Sets `user` to valid User object if token is valid
|
|||
|
|
- Sets `user` to `null` if no token or invalid token
|
|||
|
|
- Used for endpoints that work for both authenticated and anonymous users
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4. **Basic Auth Middleware** (4 tests)
|
|||
|
|
Tests Basic authentication for task endpoints.
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
✓ should reject requests without authorization header
|
|||
|
|
✓ should reject requests with Bearer instead of Basic
|
|||
|
|
✓ should reject requests with invalid secret
|
|||
|
|
✓ should accept requests with correct secret
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**What it validates:**
|
|||
|
|
- Missing Authorization header → 401
|
|||
|
|
- Wrong auth type (Bearer instead of Basic) → 401
|
|||
|
|
- Invalid secret → 401
|
|||
|
|
- Valid secret → 200 (passes through)
|
|||
|
|
|
|||
|
|
**Key behaviors:**
|
|||
|
|
- Requires `Authorization: Basic <secret>` header
|
|||
|
|
- Compares secret with `TASKS_SECRET` from config
|
|||
|
|
- Used for internal task/job endpoints
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 5. **Regular User Check Middleware** (2 tests)
|
|||
|
|
Tests that users are not temporary (read-only) accounts.
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
✓ should require auth middleware to be called first
|
|||
|
|
✓ should check if user profile exists
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**What it validates:**
|
|||
|
|
- Requires prior authentication (user must be set)
|
|||
|
|
- Checks user profile in database
|
|||
|
|
- Blocks temporary/read-only users
|
|||
|
|
- Returns 401 for temporary users with "User is read only" message
|
|||
|
|
|
|||
|
|
**Key behaviors:**
|
|||
|
|
- Must be chained after `authMiddleware`
|
|||
|
|
- Queries `profiles` table for `is_temporary` flag
|
|||
|
|
- Prevents temporary users from performing write operations
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 6. **StreamChat Middleware** (1 test)
|
|||
|
|
Tests StreamChat client injection.
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
✓ should inject StreamChat client into context
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**What it validates:**
|
|||
|
|
- StreamChat server client is available in context
|
|||
|
|
- Client is properly initialized with API key and secret
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 7. **R2 Middleware** (1 test)
|
|||
|
|
Tests Cloudflare R2 (S3-compatible) client injection.
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
✓ should inject S3 client into context
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**What it validates:**
|
|||
|
|
- S3 client is available in context for file storage
|
|||
|
|
- Client is configured with R2 credentials
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 8. **Transporter Middleware** (1 test)
|
|||
|
|
Tests email transporter injection.
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
✓ should inject email transporter into context
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**What it validates:**
|
|||
|
|
- Nodemailer transporter is available in context
|
|||
|
|
- Transporter is configured for sending emails
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 9. **Stripe Middleware** (1 test)
|
|||
|
|
Tests Stripe client injection.
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
✓ should inject Stripe client into context
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**What it validates:**
|
|||
|
|
- Stripe client is available in context
|
|||
|
|
- Client is initialized with secret key
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 10. **Stripe Sync Middleware** (1 test)
|
|||
|
|
Tests Stripe Sync engine injection.
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
✓ should inject Stripe Sync client into context
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**What it validates:**
|
|||
|
|
- Stripe Sync engine is available in context
|
|||
|
|
- Used for syncing Stripe data to database
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 11. **Middleware Chaining** (2 tests)
|
|||
|
|
Tests how middlewares work together.
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
✓ should chain multiple middlewares correctly
|
|||
|
|
✓ should stop middleware chain on auth failure
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**What it validates:**
|
|||
|
|
- Multiple middlewares can be chained
|
|||
|
|
- All dependencies are available after chaining
|
|||
|
|
- Auth failures stop the middleware chain
|
|||
|
|
- Later middlewares don't execute on early failures
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 12. **MiddlewareManager Singleton** (1 test)
|
|||
|
|
Tests the singleton pattern implementation.
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
✓ should maintain singleton instance
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**What it validates:**
|
|||
|
|
- Multiple calls to `getInstance()` return same instance
|
|||
|
|
- Configuration is initialized once
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Authentication Flow
|
|||
|
|
|
|||
|
|
### Protected Routes (authMiddleware)
|
|||
|
|
|
|||
|
|
1. Extract `Authorization` header
|
|||
|
|
2. Verify format: `Bearer <token>`
|
|||
|
|
3. Validate token with Supabase
|
|||
|
|
4. Set `user` in context
|
|||
|
|
5. Continue to next middleware/handler
|
|||
|
|
|
|||
|
|
**Failure at any step** → Return 401 immediately
|
|||
|
|
|
|||
|
|
### Optional Auth Routes (maybeAuthenticatedMiddleware)
|
|||
|
|
|
|||
|
|
1. Check for `Authorization` header
|
|||
|
|
2. If present and valid → Set `user` in context
|
|||
|
|
3. If absent or invalid → Set `user` to `null`
|
|||
|
|
4. **Always continue** to next middleware/handler
|
|||
|
|
|
|||
|
|
### Regular User Check (regularUserCheckMiddleware)
|
|||
|
|
|
|||
|
|
1. Requires `authMiddleware` first (needs `user`)
|
|||
|
|
2. Query database for user profile
|
|||
|
|
3. Check `is_temporary` flag
|
|||
|
|
4. Block if user is temporary
|
|||
|
|
5. Continue if user is regular
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Implementation Details
|
|||
|
|
|
|||
|
|
### Type Safety Considerations
|
|||
|
|
|
|||
|
|
The tests use `(c as any)` for context access because Hono's type system makes it difficult to properly type middleware context in tests. This is acceptable in tests where we need to access context variables dynamically.
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// biome-ignore lint/suspicious/noExplicitAny: Needed for context access in tests
|
|||
|
|
const supabase = (c as any).get("supabase");
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Test Structure
|
|||
|
|
|
|||
|
|
Each test follows this pattern:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
it("should <expected behavior>", async () => {
|
|||
|
|
// 1. Create Hono app
|
|||
|
|
const app = new Hono();
|
|||
|
|
|
|||
|
|
// 2. Apply middleware(s)
|
|||
|
|
app.use(middlewareManager.someMiddleware);
|
|||
|
|
|
|||
|
|
// 3. Define test route
|
|||
|
|
app.get("/test", (c) => {
|
|||
|
|
// Access context variables
|
|||
|
|
const dependency = (c as any).get("dependency");
|
|||
|
|
return c.json({ result: !!dependency });
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 4. Create test client
|
|||
|
|
const client = testClient(app) as any;
|
|||
|
|
|
|||
|
|
// 5. Make request
|
|||
|
|
const res = await client.test.$get(/* headers, etc */);
|
|||
|
|
const data = await res.json();
|
|||
|
|
|
|||
|
|
// 6. Assert expectations
|
|||
|
|
assert.strictEqual(res.status, expectedStatus);
|
|||
|
|
assert.strictEqual(data.someField, expectedValue);
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Error Handling
|
|||
|
|
|
|||
|
|
### Common Error Responses
|
|||
|
|
|
|||
|
|
| Status | Error Message | Cause |
|
|||
|
|
|--------|--------------|-------|
|
|||
|
|
| 401 | "Missing or invalid authorization header" | No `Authorization` header or wrong format |
|
|||
|
|
| 401 | "Invalid or expired token" | Token validation failed with Supabase |
|
|||
|
|
| 401 | "Unauthorized" | Basic auth secret doesn't match |
|
|||
|
|
| 401 | "User is read only" | User has `is_temporary = true` |
|
|||
|
|
| 500 | Database error message | Profile lookup failed |
|
|||
|
|
|
|||
|
|
### Error Response Format
|
|||
|
|
|
|||
|
|
All middleware errors return JSON:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
{
|
|||
|
|
"error": "Error message here"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Integration with Main Router
|
|||
|
|
|
|||
|
|
The middlewares are applied at different levels in the main router:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// Base middlewares (all routes)
|
|||
|
|
mainRouter.use(middlewareManager.supabase);
|
|||
|
|
mainRouter.use(middlewareManager.streamChat);
|
|||
|
|
mainRouter.use(middlewareManager.r2);
|
|||
|
|
mainRouter.use(middlewareManager.transporter);
|
|||
|
|
mainRouter.use(middlewareManager.stripe);
|
|||
|
|
mainRouter.use(middlewareManager.stripeSync);
|
|||
|
|
|
|||
|
|
// Auth routes (/api/v1/*)
|
|||
|
|
authRouter.use(middlewareManager.auth);
|
|||
|
|
|
|||
|
|
// Maybe auth routes (/api/v1/book/*)
|
|||
|
|
maybeAuthRouter.use(middlewareManager.maybeAuthenticated);
|
|||
|
|
|
|||
|
|
// Task routes (/api/v1/tasks/*)
|
|||
|
|
taskRouter.use(middlewareManager.basicAuth);
|
|||
|
|
|
|||
|
|
// Regular user check (specific handlers)
|
|||
|
|
factory.createHandlers(
|
|||
|
|
middlewareManager.regularUserCheck,
|
|||
|
|
async (c) => { /* handler */ }
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Testing Strategy
|
|||
|
|
|
|||
|
|
### Unit Testing
|
|||
|
|
Each middleware is tested in isolation:
|
|||
|
|
- Create minimal Hono app
|
|||
|
|
- Apply only the middleware being tested
|
|||
|
|
- Verify correct behavior with various inputs
|
|||
|
|
|
|||
|
|
### Integration Testing
|
|||
|
|
Middleware chaining is tested:
|
|||
|
|
- Multiple middlewares applied in sequence
|
|||
|
|
- Verify all dependencies are available
|
|||
|
|
- Verify error handling stops the chain
|
|||
|
|
|
|||
|
|
### Edge Cases Tested
|
|||
|
|
- Missing headers
|
|||
|
|
- Malformed headers
|
|||
|
|
- Invalid tokens
|
|||
|
|
- Empty tokens
|
|||
|
|
- Wrong auth types (Bearer vs Basic)
|
|||
|
|
- Temporary users
|
|||
|
|
- Database errors
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Future Improvements
|
|||
|
|
|
|||
|
|
1. **Mock Supabase Responses**
|
|||
|
|
- Currently tests hit real Supabase with invalid tokens
|
|||
|
|
- Could mock `supabase.auth.getUser()` for faster tests
|
|||
|
|
- Would allow testing specific Supabase error scenarios
|
|||
|
|
|
|||
|
|
2. **Performance Tests**
|
|||
|
|
- Measure middleware overhead
|
|||
|
|
- Test with many chained middlewares
|
|||
|
|
- Benchmark token validation time
|
|||
|
|
|
|||
|
|
3. **Security Tests**
|
|||
|
|
- Test token injection attacks
|
|||
|
|
- Test header manipulation
|
|||
|
|
- Test timing attacks on auth
|
|||
|
|
|
|||
|
|
4. **Error Recovery Tests**
|
|||
|
|
- Test middleware behavior on partial failures
|
|||
|
|
- Test database connection errors
|
|||
|
|
- Test third-party service failures (Stripe, StreamChat, etc.)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Related Documentation
|
|||
|
|
|
|||
|
|
- [Test Router Refactor](./TEST_ROUTER_REFACTOR.md) - How tests use main routers
|
|||
|
|
- [Middleware Initialization Fix](./MIDDLEWARE_INITIALIZATION_FIX.md) - Module initialization pattern
|
|||
|
|
- [API Tests](./API_TESTS.md) - Complete API test suite
|
|||
|
|
- [Environment Test Setup](./ENV_TEST_SETUP.md) - Test configuration
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Running the Tests
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Run all tests including middleware tests
|
|||
|
|
cd apps/api
|
|||
|
|
pnpm test
|
|||
|
|
|
|||
|
|
# Run only middleware tests
|
|||
|
|
pnpm test -- src/__tests__/middlewares/middlewares.test.ts
|
|||
|
|
|
|||
|
|
# Run with watch mode
|
|||
|
|
pnpm test:watch
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Conclusion
|
|||
|
|
|
|||
|
|
The middleware test suite provides comprehensive coverage of all authentication and dependency injection middlewares, ensuring:
|
|||
|
|
|
|||
|
|
✅ Proper authentication and authorization
|
|||
|
|
✅ Correct error handling and responses
|
|||
|
|
✅ Safe middleware chaining
|
|||
|
|
✅ Dependency injection works correctly
|
|||
|
|
✅ Edge cases are handled gracefully
|
|||
|
|
|
|||
|
|
With **116 passing tests** and **0 failures**, the API has robust middleware protection and validation.
|
|||
|
|
|