test: add unit tests for rate limiter middleware
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
# Codebase Improvements Design
|
||||
|
||||
**Date:** 2026-04--03
|
||||
**Scope:** General code quality, error handling, resilience, and maintainability improvements
|
||||
|
||||
## 1. Server Hardening
|
||||
|
||||
### 1a. Explicit DB Context Middleware
|
||||
|
||||
**File:** `src/server/index.ts`
|
||||
|
||||
Add middleware that explicitly sets `c.set("db", prodDb)` for all API routes. Currently routes call `c.get("db")` but nothing sets it in production — services silently fall back to `prodDb` via default parameters. This makes production behavior match the test pattern.
|
||||
|
||||
```ts
|
||||
import { db as prodDb } from "../db/index.ts";
|
||||
|
||||
app.use("/api/*", async (c, next) => {
|
||||
c.set("db", prodDb);
|
||||
return next();
|
||||
});
|
||||
```
|
||||
|
||||
Place this **before** the auth middleware so `db` is available when auth checks run.
|
||||
|
||||
### 1b. Route Parameter Validation
|
||||
|
||||
**New file:** `src/server/lib/params.ts`
|
||||
|
||||
Create a helper that validates numeric route params:
|
||||
|
||||
```ts
|
||||
export function parseId(raw: string): number | null {
|
||||
const id = Number(raw);
|
||||
if (!Number.isInteger(id) || id <= 0) return null;
|
||||
return id;
|
||||
}
|
||||
```
|
||||
|
||||
Update all route files (`items.ts`, `threads.ts`, `categories.ts`, `setups.ts`) to replace `Number(c.req.param("id"))` with `parseId()`, returning 400 for invalid IDs.
|
||||
|
||||
### 1c. Centralized Error Handling
|
||||
|
||||
**File:** `src/server/index.ts`
|
||||
|
||||
Add Hono's `onError` handler:
|
||||
|
||||
```ts
|
||||
app.onError((err, c) => {
|
||||
console.error(`[${c.req.method}] ${c.req.path}:`, err);
|
||||
const status = err instanceof HTTPException ? err.status : 500;
|
||||
const message = process.env.NODE_ENV === "production"
|
||||
? "Internal server error"
|
||||
: err.message;
|
||||
return c.json({ error: message }, status);
|
||||
});
|
||||
```
|
||||
|
||||
### 1d. Auth Comment Fix
|
||||
|
||||
**File:** `src/server/index.ts`
|
||||
|
||||
Change comment from:
|
||||
```
|
||||
// Auth middleware for write operations (POST/PUT/DELETE) on non-auth routes
|
||||
```
|
||||
To:
|
||||
```
|
||||
// Auth middleware for write operations (POST/PUT/PATCH/DELETE) on non-auth routes
|
||||
```
|
||||
|
||||
### 1e. Rate Limiting on Auth Endpoints
|
||||
|
||||
**New file:** `src/server/middleware/rateLimit.ts`
|
||||
|
||||
In-memory rate limiter using a `Map<string, { count: number; resetAt: number }>`:
|
||||
|
||||
- Tracks by IP (`c.req.header("x-forwarded-for") || "unknown"`)
|
||||
- 5 attempts per 15-minute window
|
||||
- Returns 429 with `{ error: "Too many attempts. Try again later." }` and `Retry-After` header
|
||||
- Stale entries cleaned on each check
|
||||
- Applied to `POST /api/auth/login` and `POST /api/auth/setup`
|
||||
|
||||
## 2. Client Resilience
|
||||
|
||||
### Error Boundary
|
||||
|
||||
**File:** `src/client/routes/__root.tsx`
|
||||
|
||||
Add `errorComponent` to the root route definition:
|
||||
|
||||
```ts
|
||||
export const Route = createRootRoute({
|
||||
component: RootLayout,
|
||||
errorComponent: RootErrorBoundary,
|
||||
});
|
||||
```
|
||||
|
||||
`RootErrorBoundary` renders a centered error message with:
|
||||
- "Something went wrong" heading
|
||||
- Error message in dev mode
|
||||
- "Try again" button that calls `router.invalidate()` + `reset()`
|
||||
|
||||
Uses TanStack Router's `ErrorComponentProps` which provides `error` and `reset`.
|
||||
|
||||
## 3. Client Refactor
|
||||
|
||||
### Split collection/index.tsx
|
||||
|
||||
Extract the three tab-level functions into separate component files:
|
||||
|
||||
| Source function | New file | Approx lines |
|
||||
|----------------|----------|-------------|
|
||||
| `CollectionView()` | `src/client/components/CollectionView.tsx` | ~260 |
|
||||
| `PlanningView()` | `src/client/components/PlanningView.tsx` | ~190 |
|
||||
| `SetupsView()` | `src/client/components/SetupsView.tsx` | ~110 |
|
||||
|
||||
`collection/index.tsx` keeps:
|
||||
- Route definition with `searchSchema` and `validateSearch`
|
||||
- `CollectionPage` function (tab switcher + AnimatePresence)
|
||||
- `TAB_ORDER` and `slideVariants` constants
|
||||
- Imports from the three new component files
|
||||
|
||||
Each extracted component is a named export, self-contained with its own hooks and local state.
|
||||
|
||||
## 4. Docs Cleanup
|
||||
|
||||
### PROJECT.md
|
||||
|
||||
**File:** `.planning/PROJECT.md`
|
||||
|
||||
Update Constraints section line:
|
||||
```
|
||||
- **Scope**: No auth, single user for v1
|
||||
```
|
||||
To:
|
||||
```
|
||||
- **Scope**: Single user with cookie/API key auth
|
||||
```
|
||||
|
||||
## Commit Strategy
|
||||
|
||||
Group into 3-4 commits by area:
|
||||
1. **Server hardening**: DB middleware, param validation, error handler, rate limiter, comment fix
|
||||
2. **Client resilience + refactor**: Error boundary, split collection route
|
||||
3. **Docs cleanup**: PROJECT.md update
|
||||
|
||||
## Testing
|
||||
|
||||
- All 183 existing tests must continue to pass
|
||||
- Rate limiter: manual verification (no automated test needed for in-memory rate limiting in a single-user app)
|
||||
- Error boundary: manual verification by triggering a render error
|
||||
- Param validation: existing route tests cover happy paths; invalid IDs are a new edge case but won't break existing tests
|
||||
Reference in New Issue
Block a user