Database & Authentication Testing Guide
Comprehensive guide for testing the database layer, authentication system, and multi-database support in SveltyCMS.
On this page
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.1exclusively 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?
- Zero Latency: No network overhead; tests run in milliseconds.
- Zero Config: No Docker or local Mongo service required.
- 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:
- Header-Based Routing: Playwright workers include an
x-test-worker-indexheader in every request. - Dynamic Connection Map: The SQLite adapter maintains a map of worker-specific connections.
- 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:
- TEST_MODE: Enabled for integration / E2E / local harnesses (
private-config-policy.ts). - Config Redirect: Automated runs load
config/private.test.tsonly — never the developer’s liveconfig/private.ts(user DB risk). - Safety Guard:
config-state.tsfails ifTEST_MODEuses a non-isolatedDB_NAMEor a name equal to liveprivate.ts. - Triple-Lock Security: Testing routes are stripped from production builds and require a cryptographic handshake for activation.
Database Test Sequence
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()andlocalStorage.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 does not run it by default (keeps the 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()andensureSystem()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.mdxfor 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 |
health-contract.test.ts |
12 | Connection health, pool stats, queryBuilder, tenant policy |
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