Skip to content

Documentation

Hook Test Coverage

Comprehensive testing documentation for all SveltyCMS middleware hooks

6/14/2026
11 min read Edit on GitHub

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:

graph TD A[Request] --> B[handleStaticAssetCaching] B --> C[handleSystemState] C --> D[handleSetup] D --> E[handleAuthentication] E --> F[handleAuthorization] F --> G[handleApiRequests] G --> H[addSecurityHeaders] H --> I[Response]

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:

  1. Create test file: tests/unit/hooks/new-hook.test.ts
  2. Follow the standard test structure
  3. Cover all code paths and edge cases
  4. Include performance tests where applicable
  5. Update this documentation

Related Documentation

testinghooksmiddlewarefail-closed
Was this page helpful?