Skip to content

Documentation

Database & Authentication Testing Guide

Comprehensive guide for testing the database layer, authentication system, and multi-database support in SveltyCMS.

9/19/2026
9 min read Edit on GitHub

Overview

SveltyCMS uses a database-agnostic architecture via the IDBAdapter interface. In 2026, we enhanced our strategy with Parallel Worker Isolation and Triple-Lock Security to ensure high performance and zero production risk.

This guide covers:

  • SQLite (Default): Primary driver for unit and integration tests.
  • Parallel Worker Isolation: How we run concurrent tests without database locks.
  • Strict Isolation: How we protect production data from test interference.
  • Network Standardization: Why we use 127.0.0.1 exclusively for testing.

The SQLite-First Strategy (2026)

Previously, local testing required a running MongoDB instance, which often led to authentication errors or “Server Busy” timeouts.

Why SQLite for Tests?

  1. Zero Latency: No network overhead; tests run in milliseconds.
  2. Zero Config: No Docker or local Mongo service required.
  3. Deterministic: Every test run starts with a fresh database.

⚡ Parallel Worker Isolation

To maximize performance, SveltyCMS supports parallel E2E testing using a Database-per-Worker strategy.

How it Works:

  1. Header-Based Routing: Playwright workers include an x-test-worker-index header in every request.
  2. Dynamic Connection Map: The SQLite adapter maintains a map of worker-specific connections.
  3. Isolated Files: Each worker operates on its own dedicated file (e.g., cms_worker1.db), eliminating “Database is locked” errors and allowing 4+ concurrent workers.

Test Architecture

Isolation Flow

SveltyCMS enforces a hard boundary between production and testing:

  1. TEST_MODE: Enabled for integration / E2E / local harnesses (private-config-policy.ts).
  2. Config Redirect: Automated runs load config/private.test.ts only — never the developer’s live config/private.ts (user DB risk).
  3. Safety Guard: config-state.ts fails if TEST_MODE uses a non-isolated DB_NAME or a name equal to live private.ts.
  4. Triple-Lock Security: Testing routes are stripped from production builds and require a cryptographic handshake for activation.

Database Test Sequence

sequenceDiagram participant Runner as Playwright Worker participant Server as Preview Server participant DB as SQLite (worker_X.db) Runner->>Server: POST /api/testing (Action: reset, Header: index=X, secret=UUID) Server->>DB: Delete/Recreate worker_X.db Runner->>Server: POST /api/testing (Action: seed, Header: index=X) Server->>DB: Initialize schema & Insert Admin Note over Runner,DB: Execute Isolated Parallel Tests

Authentication System Tests

File: tests/unit/auth/**/*.test.ts & tests/integration/api/user.test.ts

Key Test Areas:

1. Argon2id Password Security

  • ✅ Quantum-resistant hashing verification.
  • ✅ Timing attack resistance (constant-time comparisons).

2. Session & Rotation

  • ✅ Secure cookie handling.
  • ✅ Session rotation on privilege change.
  • ✅ Automatic cleanup of expired sessions.

3. Atomic Authentication

  • ✅ Clean Slate: Every test starts with page.context().clearCookies() and localStorage.clear() to prevent Admin -> Restricted role session bleed.

Running Database Tests

1. Standard Suite (SQLite, No Docker)

SQLite is the fastest local path and uses config/private.test.ts plus test database names only:

bun test --timeout 300000 tests/integration/
bun test --timeout 300000 tests/integration/api/
bun test --timeout 300000 tests/integration/databases/

2. Docker-Backed Adapter Suites

Start the matching profile from tests/docker-compose.yml before running a networked adapter:

docker compose -f tests/docker-compose.yml --profile mongodb up -d
docker compose -f tests/docker-compose.yml --profile postgresql up -d
docker compose -f tests/docker-compose.yml --profile mariadb up -d
docker compose -f tests/docker-compose.yml --profile redis up -d
Adapter Compose profile Default test credentials
MongoDB mongodb no auth on port 27017 (official image default)
PostgreSQL postgresql postgres / postgres on port 5432
MariaDB mariadb root / mariadb on port 3306
Redis redis no password on port 6379; benchmark cache variants only

Then run:

DB_TYPE=mongodb bun test --timeout 300000 tests/integration/
DB_TYPE=postgresql bun test --timeout 300000 tests/integration/
DB_TYPE=mariadb bun test --timeout 300000 tests/integration/

3. 4-DB Matrix (CI by default)

The 4-adapter integration + bench-core matrix runs in GitHub CI (ci.yml db-tests / bench-core). Local pre-push runs the SQLite suite and, when adapter/service code changed, the cross-adapter parity contracts. The full 4-DB matrix remains CI-only (keeps the default gate fast and avoids accidental live-config misuse).

bun run gate                                           # local push gate (build + SQLite)
# Multi-DB local matrix (Docker profiles required; CI is authoritative)
DB_TYPE=postgresql bun test --timeout 300000 tests/integration/
bun run test:doctor                                    # unit + SQLite integration + gate map

Env blocks and credentials: src/utils/test-db-credentials.ts (sveltycms_test / benchmark_shared — never the live sveltycms.db from private.ts).

4. Parallel E2E Testing

Playwright local runs use playwright.config.ts; it starts the ready server on 127.0.0.1:4173 and setup server on 127.0.0.1:4174 when CI is not set:

bun run build
bun x playwright test --workers=4

Troubleshooting

“UNIQUE constraint failed: roles._id”

This usually happens if multiple workers share the same database file. Fix: Ensure your request includes the x-test-worker-index and x-test-secret headers.

“Database connection failed”

Ensure config/private.test.ts has a test-scoped DB_NAME and credentials matching tests/docker-compose.yml. Prefer 127.0.0.1 in env overrides to avoid IPv6 resolution differences.


Performance & Relational Integrity (Audit)

We use the Enterprise Benchmark Matrix to validate database adapter performance under stress. The 2026 Audit update introduced:

  • Unified Seeding Contract: Implemented ensureAuth() and ensureSystem() across all adapters, ensuring roles and settings are consistent across SQLite, MariaDB, Postgres, and MongoDB.
  • Relational Benchmarks: Tests the overhead of JOINs and populations.
  • Index Pressure: Verified sub-millisecond lookups on 100,000+ entry collections.
  • Migration & Ingestion: Verified stable ingestion at 6,625 entries/s.
  • Cross-Database Leaderboard: Aggregated metrics in docs/project/benchmarks/README.mdx for side-by-side “Showdown” analysis.

Tools: scripts/benchmark-matrix/index.ts, tests/benchmarks/migration-scale.test.ts, tests/benchmarks/index-pressure.test.ts


Cross-Adapter Contract Tests (July 2026)

To ensure identical behavior across all 4 database adapters, SveltyCMS now runs 95 contract tests across 10 test suites. Every IDBAdapter method is validated for consistent return shapes, error handling, and data integrity:

Suite Tests Coverage
adapter-parity.test.ts 15 insert, findOne, findMany, update, delete, count, upsert
error-contract.test.ts 11 Duplicate keys, missing records, invalid input, error shapes
cache-contract.test.ts 9 L1 set/get/delete, TTL, tenant isolation, negative cache
transaction-contract.test.ts 5 Commit, rollback, orphan prevention
bulk-operations-contract.test.ts 10 insertMany, updateMany, deleteMany, upsertMany, atomicIncrement
advanced-crud-contract.test.ts 9 streamMany, findByIds, aggregate, restore, exists
find-page-count-contract.test.ts 9 findPage hasMore/total/keyset cursor, count modes, count cache
auth-session-amr-contract.test.ts 6 Session amr + mfaVerifiedAt multi-engine persistence & rotation
content-nodes-contract.test.ts 8 Node hierarchy, parent traversal, path queries, physical column selection
health-contract.test.ts 12 Connection health, pool stats, queryBuilder, tenant policy

Three-layer mapping: list / count product APIs {#three-layer-mapping-list—count-product-apis}

Respect the pyramid in Testing index — do not put adapter contracts in Playwright.

Layer What Files Command
Unit (white-box) Pure eligibility + page slicing + count-cache proxy (mocked L1) tests/unit/core/page-utils.test.ts, tests/unit/core/count-cache.test.ts bun run test:unit / pre-commit
Integration (black-box adapter) Real ensureFullInitialization + live CRUD on active DB_TYPE tests/integration/databases/find-page-count-contract.test.ts bun run test:integration / pre-push SQLite; CI multi-DB
E2E Only after UI/list routes call findPage (golden journey) none yet — not an adapter-only concern —
Benchmark Latency truth tables (not a gate substitute) tests/benchmarks/database-performance.test.ts BENCHMARK_RECORD=1 bun test tests/benchmarks/database-performance.test.ts
# Unit only (pure helpers)
bun test tests/unit/core/page-utils.test.ts tests/unit/core/count-cache.test.ts

# Integration contract (SQLite local default)
bun test --timeout 300000 tests/integration/databases/find-page-count-contract.test.ts

# Same contract on a Docker engine
DB_TYPE=postgresql bun test --timeout 300000 tests/integration/databases/find-page-count-contract.test.ts

Run against a specific adapter:

DB=sqlite      bun test tests/integration/databases/*-contract.test.ts
DB=mongodb     bun test tests/integration/databases/*-contract.test.ts
DB=postgresql  bun test tests/integration/databases/*-contract.test.ts
DB=mariadb     bun test tests/integration/databases/*-contract.test.ts

Benchmark harness (benchmark-harness.test.ts, 11 tests) validates the benchmark framework itself: warmup isolation, assertSuccess() on all CRUD operations, and >50% warmup failure guard. A pre-flight sanitizer (benchmark-sanitizer.ts) runs before every benchmark to verify collection cleanliness, seed integrity, and warmup isolation.

Result validation (result-validator.ts) enforces the DatabaseResult<T> contract at runtime across all integration and benchmark tests.


Benchmark Execution Strategy

Multi-Database Runner

Each benchmark test can now run across all 8 database variants (SQLite, SQLite+Redis, MariaDB, MariaDB+Redis, PostgreSQL, PostgreSQL+Redis, MongoDB, MongoDB+Redis) in a single bun test invocation.

import { runOnAllDatabases } from "./modules/benchmark-utils";

test("my test", async () => {
  await runOnAllDatabases(async (dbKey, baseUrl, dbType) => {
    // Runs once per database variant
    // Each variant gets its own fresh server
  });
}, 600000);

This replaces the previous matrix approach where the orchestrator spawned 8 separate bun test subprocesses per test file. Benefits:

Before After
464 total subprocess spawns (61 tests × 8 DBs) 61 total subprocess spawns (1 per test file)
8 servers per test (each test started its own) 8 servers per test (same, but one bun test process)
Failure cascades across database variants Each database is isolated — failure stops only that variant
Redis connection accumulation across subprocesses Redis connections cleaned up per variant

Known-Incompatible Tests

Some tests are incompatible with specific database engines. The skipOn field in scripts/benchmark-matrix/benchmark-scripts.ts prevents them from running on broken combinations:

{
  path: "tests/benchmarks/state-machine-transition.test.ts",
  skipOn: ["mongodb"],  // MongoDB connection drops during 50-cycle reinitialize stress
}
Test Skipped On Reason
state-machine-transition MongoDB Connection drops during self-healing stress cycles
data-residency-failover MongoDB ConnectionRefused after failover simulation
database-failover MongoDB Connection doesn’t recover cleanly enough for failover timing

Bun Runtime Alignment

All 4 database adapters now run natively under bun test — no vitest/Node.js special case for MongoDB:

Adapter Driver Previously Now
SQLite bun:sqlite (native) bun test bun test
MariaDB mysql2 (pure JS) bun test bun test
PostgreSQL postgres (pure JS) bun test bun test
MongoDB mongoose → bson vitest (Node.js) bun test ✅

The bson package calls v8.isBuildingSnapshot() at module load time, which Bun (JavaScriptCore) doesn’t implement. A shim in src/utils/v8-shim.ts patches process.getBuiltinModule('v8') to return a safe stub.


Related Documentation

testingdatabaseauthenticationsqlitemongodb
Was this page helpful?