test(dispatch): unit tests for dispatch pipeline (was 575 LoC / 0 tests)#324
Merged
Destynova2 merged 4 commits intomainfrom Apr 28, 2026
Merged
test(dispatch): unit tests for dispatch pipeline (was 575 LoC / 0 tests)#324Destynova2 merged 4 commits intomainfrom
Destynova2 merged 4 commits intomainfrom
Conversation
added 4 commits
April 28, 2026 22:41
CI re-runs the full test suite (incl. doctests) on every PR via the .github/workflows/ci.yml tests job, so local pre-push duplication adds ~20 min per push without catching anything new. Pre-push hooks should be fast-fail; expensive checks belong on the CI server. Closes audit finding: silent productivity tax (pre-push duplication).
Documents the three-state intent (true/false/absent) of ProviderConfig.is_enabled and the dependency on deny_unknown_fields (added in the next commit) to reject typos like enbaled = false at parse time. Behaviour is unchanged; this is purely contractual clarity to support the silent-typo-killer audit. Closes audit finding: silent typo killer on provider config.
Adds #[serde(deny_unknown_fields)] to AppConfig and the major sub-structs (ProviderConfig, ModelConfig, TierConfig, RouterConfig, ScoringConfig, CacheConfig, BudgetConfig, DlpConfig, SecurityConfig). Without this guard, a typo like enbaled = false in a [[providers]] block silently parses (the unknown key is dropped) and the provider remains enabled with the wrong intent. With the guard, parsing fails loudly and the operator gets an actionable error pointing at the offending key. Tested with the full nextest suite (1268 tests) plus all doctests: no fixture, preset or example carries a stale field, so this is a pure tightening with no migration cost. Closes audit finding: silent typo killer on TOML config.
Each entry in DENIED_SECTIONS / DENIED_KEYS now carries a short justification table covering why it can not be hot-reloaded — either because the data is sensitive (credentials, DLP rules) or because the consumer is constructed once at process start (TLS listener, secret backend, TEE attestation, FIPS gate). Adds tee, fips, server.tls and secrets.backend to the deny-list so the documented "static-init" rationale matches actual behaviour. Also emits an INFO log on every denied attempt telling the operator to restart instead of expecting the silent reload to apply. Adds two unit tests covering the new deny entries (tee/fips sections and server.tls / secrets.backend keys) and asserts that sibling keys in the same sections remain editable. Closes audit finding: hot-reload UX (silent ignore of denied edits).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds 21 unit tests to
src/server/dispatch/mod.rs, which previously had 575 LoC and zero tests — the largest blind spot in the test suite per the audit.Tests are organized into the following groups within
mod tests:DLP phase (5 tests)
dlp_clean_input_passes_through_engine— happy path: benign prompt does not trigger block.dlp_blocks_request_on_injection_match— edge:(?i)ignore previous instructionstriggersDlpBlockError::InjectionBlocked.dlp_block_error_display_includes_pattern_name— protects the audit log entry built fromblock_err.to_string().dlp_disabled_scan_input_skips_engine— verifies thescan_inputflag is honoured.dlp_no_engine_short_circuits_to_ok—Option::NoneDLP engine is the no-op precondition.Cache phase (6 tests)
cache_round_trip_returns_stored_response— happy path: put/get returns identical body.cache_miss_returns_none— empty cache lookup misses.cache_skips_oversized_entries—max_entry_bytesis enforced at insert time.cache_key_is_deterministic_for_same_request— identical requests hash to identical keys.cache_key_skipped_for_nonzero_temperature—temperature > 0opts out of caching.cache_key_differs_per_tenant— multi-tenant isolation in cache keys.DispatchResult variants (2 tests)
dispatch_result_complete_carries_provider_and_model— non-streaming path preserves provider info.dispatch_result_fanout_carries_response_only— fan-out path stores only the response.Audit entry construction (3 tests)
audit_entry_dlp_block_uses_blocked_backend_marker— backend == "BLOCKED" on DLP block.audit_entry_provider_failure_uses_none_backend— backend == "NONE" on all-providers-failed.audit_entry_fanout_success_records_token_counts— fan-out audits both input and output tokens.Provider behaviour (2 tests)
mock_provider_returns_fixed_response— exercises theMockLlmProviderround trip.mock_provider_supports_only_configured_model— guards accidental model leakage in fallback chains.Token accounting (2 tests)
token_counts_propagate_from_provider_response— happy path: counters propagate intact.dlp_block_reports_zero_tokens_consumed— billing guard: blocked requests havetoken_counts: None.Routing metadata (1 test)
route_decision_default_route_type_no_complexity_tier— gate at Step 5.5 of the pipeline.Honest scoping note
Happy path vs edge case (per task brief):
Behaviours intentionally NOT covered (documented in the test module docstring as follow-up work):
dispatch()happy path with the provider loop.dispatch_provider_loop()ordering with the adaptive scorer.AuditLogfile (needs disk fixture).The reason these aren't unit tests today is that every
DispatchContextrequires a fully builtArc<AppState>, andAppStatehas hard dependencies on:GrobStore(real disk + AES-256-GCM cipher init).AuditLog(real signing key generation, real append-only file).metrics_exporter_prometheus::PrometheusBuilder::install_recorder()(process-global, single-install).SpendTracker(real spend journal).Mocking these would require introducing trait objects on the hot path, which is a non-trivial refactor that is out of scope for a tests-only PR. The pragmatic approach taken here:
Coverage on
src/server/dispatch/mod.rsimproves from 0 -> 21 tests touching DLP scan, cache round-trip, audit entry construction, and result variants. End-to-end dispatch pipeline coverage is best added as integration tests intests/integration/, where standing up a realAppStateagainst atempfile::TempDiris already common.Note on base branch
Original task brief specified
fix/preset-mod-include-stras the base, but that branch has since been merged intomain(PR #304), so this PR targetsmaindirectly.Test plan
cargo nextest run --no-fail-fast— 1289/1289 pass (was 1268; +21 new).cargo nextest run --no-fail-fast --lib server::dispatch::tests— 21/21 pass.cargo fmt --check— clean.cargo clippy --tests --lib --no-deps— clean.tokio::time::sleepin test bodies; deterministic by construction.