Test coverage
Every Hortval release ships only after the full automated test suite passes against the targeted database backends. This page lists the suites that compose the gate, what each one verifies, and the headline counts at the time of writing.
Headline numbers
| Category | Tests | What it verifies |
|---|---|---|
| Unit (TU) | 865 | Pure logic: configuration parsing and validation, policy resolution, JWS signing and verification, anti-replay nonces, DNS scope matching, CSR validation, key handling, the asynchronous job engine, licensing decisions, rate-limit decision tables, audit-line encoding. No I/O, no database. |
| Integration (IT) | 188 | Real database (SQLite, PostgreSQL, SQL Server), real audit file on disk, real PKI request store, full ACME handler stack wired against the storage layer. Each test runs against every supported database backend. |
| End-to-end (E2E) | 167 | The full Hortval binary running as a subprocess. Two flavours: (1) CLI black-box — every subcommand (serve, init, validate, migrate, license, cold-start, backup, audit, adcs check), exit codes, error messages, each run against every supported database backend. (2) ACME protocol — real third-party clients (lego, certbot, acme.sh) plus a RFC-strict native client driving certificate issuance, renewal, revocation, account lifecycle, key rollover, and the full error/security path. |
| Total | 1220 |
Numbers are refreshed at every release. The count above reflects the v0.9.5 line — +168 tests since v0.9.4.
These count test functions, not executions. Most run several times — once per database backend — so a full run reports a much larger figure: the v0.9.5 Windows run executed 5,680 tests across 39 phases, with 80 skipped and no failure.
Every skip — including a skipped sub-case inside a larger test — is declared in a reference kept under version control, and every full run compares against it in both directions: a test that starts skipping without being declared, and a declared skip that no longer happens. Coverage cannot quietly erode between releases, and a test that comes back to life is noticed rather than assumed.
What is covered, by area
ACME protocol (RFC 8555 + RFC 9773)
- Account: create, lookup-by-key, contact update, deactivate, key rollover
(including the
409 Conflictpath when rolling to an already-used key). - Order: create, get, finalize, state machine transitions, expiry.
- Authorization + challenges: HTTP-01, DNS-01, TLS-ALPN-01 — happy path and every documented failure mode.
- Wildcards: pure
*.zoneand mixedzone + *.zonein a single order. - Renewal information (ARI): suggested window,
Retry-After, revoked-cert collapse to immediate renewal. - Revocation: client-signed and account-key-signed paths, double-revoke rejection, and — against Microsoft ADCS — real propagation to the certificate authority (see below).
- URL and header conformance: every endpoint where RFC 8555 requires a
Locationheader is asserted on the wire. - Anti-replay nonces: single-use enforcement, validity under concurrent clients running at uneven speeds, and both window edges.
- Account keys: RSA, ECDSA and Ed25519. One key yields one account, whatever the encoding of its JWK.
ACME client interoperability
E2E suite runs the full happy-path issuance against:
- lego — HTTP-01, DNS-01, TLS-ALPN-01.
- certbot — HTTP-01, DNS-01.
- acme.sh — HTTP-01, DNS-01, TLS-ALPN-01.
- A built-in, RFC-strict native client for the paths the third-party CLIs do not exercise: error-path, security probes, account lifecycle operations, and RFC URL/header conformance.
One scenario runs the three CLIs simultaneously against a single server, each solving a different challenge type. Another issues and renews an ECDSA certificate under an ECDSA account key.
Microsoft ADCS integration
When an ADCS lab is available, Hortval is validated against a real Active Directory Certificate Services authority:
- Issuance through both supported ADCS connectors, so an upgrade never changes behaviour silently.
- Revocation propagated to the CA, then confirmed on the authority itself — a revoked certificate is verified as revoked at the source, not only in Hortval's own records.
- Onboarding checks — the
adcs checkcommand and the setup wizard verify that the CA is reachable, that the certificate template is published, and read the template's key requirements, so common misconfigurations surface at setup time instead of as opaque startup failures. - Clear diagnostics — when the CA refuses a request, the underlying reason is surfaced with actionable guidance (for example, a key that does not meet the template's requirements), independent of the CA's display language.
- Server-certificate key selection — Hortval's own certificate can be issued as RSA or ECDSA at the strength the CA template mandates, and this selection is verified end to end.
- Key-algorithm routing — an ADCS template can pin the key algorithm, and ACME clients disagree on their default: some generate ECDSA, others RSA. Where a separate template is declared for each, a certificate is issued from the one matching the client's key. Verified against a real CA carrying two algorithm-pinned templates, with clients of both families in the same run.
Database support
The integration suite runs the same test set against every supported backend:
- SQLite — always.
- PostgreSQL — when configured in the CI environment.
- SQL Server — when configured in the CI environment, including integrated authentication: Hortval connects as its own Windows service account, with no password in the configuration file. That path is exercised against a domain-joined SQL Server, and the suite refuses to run unless the server itself confirms the session was authenticated by Kerberos under a Windows account — a password left in the connection string cannot pass for it.
The CLI black-box suite runs against all three as well: migrate, the schema
gate, the cold-start plans and license install are each exercised on every
supported engine. The one exception is backup, SQLite-only by design in v1.
Concurrent-writer behaviour (serialisable retry) and dialect-specific edge cases (UUID handling, NULL semantics in unique indexes, cascade chain restrictions on SQL Server) are all covered.
Test isolation is per-schema rather than per-database on PostgreSQL and SQL
Server: each test gets its own schema, and on SQL Server its own login and user
so unqualified identifiers resolve there rather than falling back to dbo.
Schema and migrations
The upgrade path is treated as a component in its own right, because its failures are silent and reach a database nobody can inspect afterwards.
- Every upgrade path between versions, on the three backends: applied in order, out of order, partially, and interrupted mid-run.
- The startup contract: additive changes applied automatically, a breaking change refused with the remedy named, a database newer than the binary refused, and the mixed case a failed upgrade followed by a rollback leaves.
- A migration already applied is verified against the binary that meets it, so a schema that no longer matches the code is refused rather than used.
- Every table created by a migration is checked to be declared, in both directions — an undeclared table and a declared table that no longer exists both fail the suite.
- The generated SQL (
hortval migrate --sql) is compared against what a real migration executes, statement by statement, and applied end-to-end to confirm the resulting database satisfies the binary. - Schema resolution on PostgreSQL and SQL Server is asserted against the engines themselves rather than assumed from their documentation: where an unqualified name resolves, where a table is created, and which schema wins when the same name exists twice. Hortval's behaviour on a shared database rests on those answers, so they are re-checked at every release.
Asynchronous issuance & revocation
Certificate signing and revocation against a CA run through an asynchronous job engine, and its reliability guarantees are tested:
- Retries with escalating back-off when the CA is momentarily unavailable, up to a bounded window before a request is marked failed.
- Idempotent processing — a job that is retried, or replayed after a crash, never issues or revokes twice.
- The full issuance and revocation job lifecycle, including the audit record emitted at each terminal outcome.
Server certificate management
Hortval manages its own TLS certificate, and each source is exercised:
- Static files — loaded from disk and hot-reloaded when they change.
- Internal PKI — issued and automatically renewed from a configured authority, with the key algorithm and strength selectable to match the CA template (see Microsoft ADCS integration).
- Let's Encrypt — automatic ACME issuance.
- Startup acquisition, renewal timing, and local caching are all covered.
Resilience & recovery
- Node identity — each server instance has a stable identity that anchors its audit trail; work in flight is recovered correctly across a process restart.
- Graceful shutdown — in-flight requests and jobs drain within the configured window, and terminal database writes survive a shutdown signal.
- The built-in test authority is flagged — selecting it produces an unconditional warning, surfaced by both configuration validation and startup.
- Certificate authority material is never overwritten — for the built-in test authority, every startup path is covered: absent key, correct password, wrong password, corrupt key, empty files, missing certificate, certificate not matching the key, changed common name, unreadable certificate. Each refusal asserts both files are unchanged byte for byte, and an authority written by the previous version starts unchanged on the current one.
Configuration
- The configuration parser is covered for strict schema checking (unknown fields are rejected), quoting and escaping, and Windows path handling.
hortval validateruns exactly the same static validation as the server's fail-fast boot gate, with no side effects — an invalid configuration is caught before startup rather than halfway through it.- A partial section leaves the other keys at their documented defaults; an explicit value is honoured or rejected, never silently replaced.
Rate limiting
End-to-end suite (7 tests) under a tight profile, covering global denial, account-creation throttling, order-creation throttling, duplicate-certificate refusal, failed-validation back-off, and the pending-authorization cap.
A further 22 tests cover the two per-address buckets: refusal of a misbehaving address, isolation of its neighbours, whitelist exemption, threshold weighting, and the outcomes that deliberately do not count as abuse (stale nonce, expired order, licence refusal).
Audit log
- Round-trip write + verify on every supported database backend.
- The audit secret is redacted from database debug logs, checked on both log formats, and stored and read back unchanged on every supported backend.
- HMAC chain anchoring on the installation key.
- Recovery across process restart, including rotated files. Rotation writes dated segments that are never renamed.
- Tampering detection (line removed, line modified, MAC altered, wrong installation key).
- Every protocol event (account create / key change / deactivate, order create / finalize / invalid, authorization & challenge validate, certificate issue / revoke, rate-limit deny, license deny) is asserted to fire exactly once at the right point in the request lifecycle.
License enforcement
Licensing is enforced at startup and at runtime, including the recovery paths that keep a server running through a degraded or expired state.
CLI
Every subcommand and flag is exercised in the E2E suite — including
serve, init, validate, migrate, license, backup, audit verify,
and the ADCS preflight adcs check: argument validation, exit codes, error
messages on missing file / bad format / incompatible flag combinations,
help output for every command level.
Backup & restore
- SQLite snapshot round-trip with schema verification.
- Integrity check (
quickandfull). - Refusal to overwrite an existing target.
- Verify against a corrupted file / missing tables / wrong driver.
Cross-platform
The suite runs on Linux and macOS for every release, and the whole suite runs on Windows — not a subset. The v0.9.5 Windows run executed 39 phases across the three database backends with no failure.
That matters because Windows is where the product is deployed, and several behaviours exist only there: where files land once the working directory is resolved, how anchored paths expand, and how the process drains when asked to stop. Each is covered on Windows itself, against all three database backends.
On Windows, when an ADCS lab is available, the protocol suite additionally runs against a real Active Directory Certificate Services authority — covering both certificate issuance and revocation, and both ADCS connectors (see Microsoft ADCS integration).
How the categories are defined
Classification is purely based on the file path of the test, so the numbers are reproducible without judgement calls:
- End-to-end (E2E) — tests that run the Hortval binary as a subprocess and assert on the wire or the CLI output.
- Integration (IT) — tests that hit a real database, write a real audit file to disk, or wire the full handler stack against a real storage backend.
- Unit (TU) — every other test: pure-function logic with no I/O.
A test that touches both a database and a real subprocess counts as E2E (the more demanding category wins).
Where the numbers come from
The full suite is launched from the repository root with a single command that records pass / fail / skip per module and writes per-module logs for diagnostics. The headline numbers above come from enumerating the whole test suite and classifying each test by an objective, path-based rule.

