Hook Test Coverage
Comprehensive testing documentation for all SveltyCMS middleware hooks
On this page
Complete test coverage for all 17 SvelteKit middleware hooks and security test suites in the SveltyCMS system.
Test Suite Overview
| Test Suite | Tests | Coverage | File |
|---|---|---|---|
| Static Asset Caching | 6 | ✅ Complete | tests/unit/hooks/static-asset-caching.test.ts |
| System State | 26 | ✅ Complete | tests/unit/hooks/system-state.test.ts |
| System State Security | 14 | ✅ Complete | tests/unit/hooks/system-state-security.test.ts |
| Setup | 16 | ✅ Complete | tests/unit/hooks/setup.test.ts |
| Test Suite | Tests | Coverage | File |
|---|---|---|---|
| Authentication | 33 | ✅ Complete | tests/unit/hooks/authentication.test.ts |
| API Keys Authentication | 12 | ✅ Complete | tests/unit/hooks/api-keys-authentication.test.ts |
| Bearer Authentication | 18 | ✅ Complete | tests/unit/hooks/bearer-authentication.test.ts |
| Guest Authentication | 8 | ✅ Complete | tests/unit/hooks/guest-authentication.test.ts |
| Magic Links Authentication | 10 | ✅ Complete | tests/unit/hooks/magic-links-authentication.test.ts |
| Test Suite | Tests | Coverage | File |
|---|---|---|---|
| Authorization | 23 | ✅ Complete | tests/unit/hooks/authorization.test.ts |
| API Requests | 28 | ✅ Complete | tests/unit/hooks/api-requests.test.ts |
| Token Resolution | 5 | ✅ Complete | tests/unit/hooks/token-resolution.test.ts |
| Security Headers | 38 | ✅ Complete | tests/unit/hooks/security-headers.test.ts |
| CSP Headers | 10 | ✅ Complete | tests/unit/hooks/csp-headers.test.ts |
| Defense-in-Depth | 25 | ✅ Complete | tests/unit/hooks/defense-in-depth.test.ts |
| Adversarial Testing | 20 | ✅ Complete | tests/unit/hooks/adversarial.test.ts |
| Total | 317 | 100% | 16 files |
Performance Benchmarks (Audit)
All hooks are benchmarked via tests/benchmarks/hooks-performance.test.ts which measures 3 pipeline layers under production load (600+ iterations each):
| Metric | Latest (SQLite) | Budget | What it measures |
|---|---|---|---|
| Turbo Pipeline (Light) | 2.55ms avg | <2ms | Health check — fastest path |
| Full Security + Auth | 5.47ms avg | <2ms | JWT + session + RBAC |
| Mutation + Audit Logging | 1.90ms avg | <2ms | Write with fire-and-forget audit |
| Auth Overhead | 0.66ms | — | Turbo → Full Auth delta |
| Compression Ratio (br) | ~65% avg | — | Brotli on cache HIT payloads |
| Compression Ratio (gzip) | ~58% avg | — | Gzip on cache HIT payloads |
Note: Audit is now fire-and-forget (context captured before resolve, logged in detached promise). HOOK_TIMING_ENABLED gate disables per-request instrumentation in production and under SVELTY_BENCHMARK_SUITE for cleaner benchmarks. Compression metrics captured via X-* headers.
Middleware Sequence
The hooks run in this specific order:
Test Patterns and Best Practices
Standard Test Structure
All hook tests follow this consistent pattern:
import { describe, it, expect, beforeEach, mock } from "bun:test";
import { hookName } from "@src/hooks/hookName";
import type { RequestEvent } from "@sveltejs/kit";
function createMockEvent(pathname: string, ...args): RequestEvent {
// Create consistent mock events with mandatory locals.roles and locals.tenantId
}
describe("hookName Middleware", () => {
let mockResolve: ReturnType<typeof mock>;
beforeEach(() => {
mockResolve = mock(() => Promise.resolve(new Response("OK")));
});
describe("Feature Category", () => {
it("should do something specific", async () => {
const event = createMockEvent("/path");
const response = await hookName({ event, resolve: mockResolve });
expect(response).toBeDefined();
});
});
});
Mock Event Creation
Each test file has a createMockEvent() helper that creates consistent RequestEvent mocks:
function createMockEvent(
pathname: string,
method: string = "GET",
headers: Record<string, string> = {},
cookies: Record<string, string> = {},
): RequestEvent {
const url = new URL(pathname, "http://localhost");
const requestHeaders = new Headers(headers);
return {
url,
request: new Request(url.toString(), { method, headers: requestHeaders }),
cookies: {
get: (name: string) => cookies[name],
set: mock(() => {}),
delete: mock(() => {}),
},
locals: {
tenantId: "test-tenant",
roles: [{ name: "admin", isAdmin: true, permissions: [] }],
},
} as unknown as RequestEvent;
}
Individual Hook Test Coverage
1. handleStaticAssetCaching
Purpose: Aggressive caching for static assets (1 year max-age)
Test Categories:
- ✅ Static Asset Detection (STATIC_ASSET_REGEX)
- ✅ isStaticAsset() Function
- ✅ Cache Header Application
- ✅ Non-Static Asset Passthrough
- ✅ Edge Cases (query params, hash fragments)
Key Tests:
it("should match /_app/ paths", () => {
expect("/_app/immutable/chunks/index.js").toMatch(STATIC_ASSET_REGEX);
});
it("should add aggressive cache headers for static assets", async () => {
const event = createMockEvent("/_app/immutable/chunks/index.js");
const response = await handleStaticAssetCaching({ event, resolve: mockResolve });
expect(response.headers.get("Cache-Control")).toBe("public, max-age=31536000, immutable");
});
2. handleSystemState
Purpose: System state machine (IDLE, INITIALIZING, READY, DEGRADED, FAILED)
Test Categories:
- ✅ State Transitions
- ✅ Route Blocking by State
- ✅ Health Checks
- ✅ Setup Route Access
- ✅ Error Responses
Key Tests:
it("should block routes when state is INITIALIZING", async () => {
mockGetSystemState.mockReturnValue("INITIALIZING");
const event = createMockEvent("/dashboard");
try {
await handleSystemState({ event, resolve: mockResolve });
} catch (err: unknown) {
expect((err as Error).message).toContain("initializing");
}
});
3. handleSetup
Purpose: Setup wizard flow enforcement (3-step validation)
Test Categories:
- ✅ Setup State Detection
- ✅ Allowed Routes During Setup
- ✅ Redirect to Setup
- ✅ Block Setup After Completion
- ✅ Config Validation (JWT_SECRET_KEY, DB_HOST, DB_NAME)
- ✅ Cookie Handling
- ✅ Multi-Step Validation
Key Tests:
it("should redirect /dashboard to /setup when config missing", async () => {
const event = createMockEvent("/dashboard", false);
try {
await handleSetup({ event, resolve: mockResolve });
} catch (err) {
expect(err).toBeDefined(); // Redirect expected
}
});
it("should allow /setup route when setup incomplete", async () => {
const event = createMockEvent("/setup", false);
const response = await handleSetup({ event, resolve: mockResolve });
expect(response.status).toBe(200);
});
4. handleAuthentication
Purpose: Enterprise session management with 3-layer caching
Test Categories:
- ✅ Public Route Bypass
- ✅ Internal Route Bypass
- ✅ Multi-Tenancy Detection
- ✅ Session Validation
- ✅ 3-Layer Cache (Memory → Redis → DB)
- ✅ WeakRef Cache Management
- ✅ Session Rotation (15-minute interval)
- ✅ Tenant Isolation
- ✅ Metrics Tracking
Key Tests:
it("should skip authentication for /login", async () => {
const event = createMockEvent("/login");
await handleAuthentication({ event, resolve: mockResolve });
expect(mockResolve).toHaveBeenCalled();
});
it("should extract tenantId from hostname (subdomain)", async () => {
const event = createMockEvent("/dashboard", "session123", "tenant1.example.com");
await handleAuthentication({ event, resolve: mockResolve });
expect(mockResolve).toHaveBeenCalled();
});
5. handleAuthorization
Purpose: Permission checks and role validation
Test Categories:
- ✅ Public Route Access
- ✅ Authenticated User Access
- ✅ Unauthenticated User Handling
- ✅ Role Caching
- ✅ User Count Caching
- ✅ Permission Checks (hasManageUsersPermission)
- ✅ Redirect to Setup When No Roles
- ✅ OAuth Route Handling
Key Tests:
it("should redirect to /login for protected routes", async () => {
const event = createMockEvent("/dashboard");
try {
await handleAuthorization({ event, resolve: mockResolve });
} catch (err) {
expect(err).toBeDefined(); // Redirect expected
}
});
it("should redirect to /setup when no roles found", async () => {
const event = createMockEvent("/dashboard");
try {
await handleAuthorization({ event, resolve: mockResolve });
} catch (err) {
expect(err).toBeDefined();
}
});
6. handleApiRequests
Purpose: API role-based access via the Fail-Closed Dispatcher and intelligent caching
Test Categories:
- ✅ Non-API Route Passthrough
- ✅ Setup API Exemption
- ✅ Authentication Requirement
- ✅ Role-Based API Access (hasApiPermission)
- ✅ GET Request Caching
- ✅ Cache Bypass (?refresh=true, ?nocache=true)
- ✅ GraphQL Bypass
- ✅ Cache Invalidation on Mutations
- ✅ Streaming Optimization
- ✅ Metrics Tracking
Key Tests:
it("should cache successful GET responses", async () => {
const event = createMockEvent("/api/collections", "GET", mockUser);
const response = await handleApiRequests({ event, resolve: mockResolve });
expect(response.headers.get("X-Cache")).toBeDefined();
});
it("should invalidate cache on POST", async () => {
const event = createMockEvent("/api/collections", "POST", mockUser);
await handleApiRequests({ event, resolve: mockResolve });
// Cache invalidated for /api/collections/*
expect(mockResolve).toHaveBeenCalled();
});
7. addSecurityHeaders
Purpose: HTTP security headers (CSP, HSTS, X-Frame-Options, etc.)
Test Categories:
- ✅ X-Frame-Options Header
- ✅ X-Content-Type-Options Header
- ✅ Referrer-Policy Header
- ✅ Permissions-Policy Header
- ✅ Strict-Transport-Security (HSTS) for Production HTTPS
- ✅ CSP Handling (SvelteKit Native)
- ✅ Static Asset Handling
- ✅ HTTP vs HTTPS Behavior
Key Tests:
it("should add X-Frame-Options: SAMEORIGIN header", async () => {
const event = createMockEvent("/dashboard");
const response = await addSecurityHeaders({ event, resolve: mockResolve });
expect(response.headers.get("X-Frame-Options")).toBe("SAMEORIGIN");
});
it("should add HSTS header for HTTPS in production", async () => {
const event = createMockEvent("/dashboard", "https:");
const response = await addSecurityHeaders({ event, resolve: mockResolve });
const hsts = response.headers.get("Strict-Transport-Security");
expect(hsts).toContain("max-age=31536000");
});
8. Token Resolution
Purpose: Bearer token validation, resolution, and multi-tenant isolation
Test Categories:
- ✅ Token parsing and validation
- ✅ Bearer token extraction
- ✅ Token expiry handling
- ✅ Multi-tenant token isolation
9. CSP Headers
Purpose: Content Security Policy enforcement to prevent XSS and data injection
Test Categories:
- ✅ CSP header presence and values
- ✅ Script source restrictions
- ✅ Style source restrictions
- ✅ Report-URI configuration
10. Defense-in-Depth
Purpose: Multi-layer security verification — middleware, dispatcher, handler, and page action enforcement
Test Categories:
- ✅ Middleware-level blocking
- ✅ Dispatcher fail-closed authorization
- ✅ Handler-level admin verification
- ✅ Page action permission guards
- ✅ Cookie prefix hardening (
__Host-) - ✅ Setup completion gating
- ✅ Media permission checks (write/delete)
11. Adversarial Testing
Purpose: Fuzz testing and malicious input handling for all hook layers
Test Categories:
- ✅ Malformed header injection
- ✅ Path traversal attempts
- ✅ Encoding-based bypass attempts
- ✅ HTTP method smuggling
- ✅ Large payload rejection
- ✅ Concurrent connection fuzzing
12. Authentication Sub-Suites
The following files test specific authentication mechanisms beyond the core handleAuthentication hook:
| Test Suite | File |
|---|---|
| API Keys Authentication | tests/unit/hooks/api-keys-authentication.test.ts |
| Bearer Authentication | tests/unit/hooks/bearer-authentication.test.ts |
| Guest Authentication | tests/unit/hooks/guest-authentication.test.ts |
| Magic Links Authentication | tests/unit/hooks/magic-links-authentication.test.ts |
13. System State Security
Purpose: Security-specific system state checks — unauthorized access during init, degraded, and failed states
Test Categories:
- ✅ Route blocking during non-READY states
- ✅ Graceful degradation responses
- ✅ Setup route access during INITIALIZING
- ✅ Tenant isolation under failure
Running the Tests
Run All Hook Tests
bun test tests/unit/hooks/
Run Individual Hook Test
bun run test:unit -- static-asset-caching
bun run test:unit -- authentication
bun run test:unit -- security-headers
Run with Coverage
bun test --coverage tests/unit/hooks/
Watch Mode
bun test --watch tests/unit/hooks/
Test Metrics
Current Status
- Total Hook Tests: 317+
- Test Files: 16
- Average Tests per File: 20
- Test Framework: Vitest (vi) / Bun Test v1.3.10
Coverage by Category
| Category | Tests | Status |
|---|---|---|
| Static Asset Caching | 6 | ✅ Complete |
| System State Management | 26 | ✅ Complete |
| System State Security | 14 | ✅ Complete |
| Setup Wizard | 16 | ✅ Complete |
| Authentication (all) | 81 | ✅ Complete |
| Authorization | 23 | ✅ Complete |
| API Request Handling | 28 | ✅ Complete |
| Token Resolution | 5 | ✅ Complete |
| Security Headers | 38 | ✅ Complete |
| CSP Headers | 10 | ✅ Complete |
| Defense-in-Depth | 25 | ✅ Complete |
| Adversarial Testing | 20 | ✅ Complete |
Common Test Patterns
Testing Redirects
it("should redirect to /login for protected routes", async () => {
const event = createMockEvent("/dashboard");
try {
await handleAuthorization({ event, resolve: mockResolve });
expect(true).toBe(false); // Should not reach here
} catch (err) {
expect(err).toBeDefined(); // Redirect throws
}
});
Testing Error Responses
it("should return 401 for unauthenticated API requests", async () => {
const event = createMockEvent("/api/collections");
try {
await handleApiRequests({ event, resolve: mockResolve });
} catch (err) {
expect(err).toBeDefined(); // Error thrown
}
});
Testing Cache Operations
it("should cache successful GET responses", async () => {
const event = createMockEvent("/api/data", "GET", mockUser);
const response = await handleApiRequests({ event, resolve: mockResolve });
expect(response.headers.get("X-Cache")).toBe("MISS");
});
Testing Headers
it("should add security headers", async () => {
const event = createMockEvent("/dashboard");
const response = await addSecurityHeaders({ event, resolve: mockResolve });
expect(response.headers.get("X-Frame-Options")).toBe("SAMEORIGIN");
expect(response.headers.get("X-Content-Type-Options")).toBe("nosniff");
});
Continuous Integration
All hook tests run automatically on:
- Pull requests
- Commits to main branch
- Pre-release validation
# .github/workflows/test.yml
- name: Run Hook Tests
run: bun test tests/unit/hooks/
Contributing
When adding new middleware hooks:
- Create test file:
tests/unit/hooks/new-hook.test.ts - Follow the standard test structure
- Cover all code paths and edge cases
- Include performance tests where applicable
- Update this documentation
Related Documentation