Keep server tests off the developer's real ~/.fabro/storage

Test settings usually omit `[server.storage] root`, so it resolved to the
production default. Handlers that walk that tree read whatever the machine
happened to have.

That is why all_spec_routes_are_routable was slow. Timing every request in
it showed 91% of the runtime in two routes:

  6304ms  GET /api/v1/system/resources
  4574ms  GET /api/v1/system/df
   583ms  POST /api/v1/system/prune/runs
  ...
  the remaining 134 operations: 8ms combined

Both size Fabro-managed storage. On this machine that meant 193MB and 90,795
entries under scratch/, so the test's duration tracked how long the developer
had been running Fabro locally. Run-creating tests were writing there too.

Redirect settings that still carry the production default to a `storage`
directory beside the test vault, alongside the existing `server.env` and
`settings.toml` siblings. A test that chose its own root keeps it.

all_spec_routes_are_routable drops from ~15s to 0.6s, and the full workspace
run from ~33s to ~21s. All 7402 tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-07-27 18:39:25 -04:00
parent 7950441bee
commit 99dd7718c0
No known key found for this signature in database

View file

@ -13,6 +13,7 @@ use axum::middleware::Next;
use axum::response::Response;
use axum::{Router, middleware};
use chrono::Duration as ChronoDuration;
use fabro_config::user::default_storage_dir;
use fabro_config::{RunLayer, ServerSettingsBuilder, Storage, envfile};
use fabro_db::DbPool;
use fabro_interview::Interviewer;
@ -253,9 +254,10 @@ impl TestAppStateBuilder {
self.try_build().expect("test app state should build")
}
pub fn try_build(self) -> anyhow::Result<Arc<AppState>> {
pub fn try_build(mut self) -> anyhow::Result<Arc<AppState>> {
let (store, artifact_store) = self.store_bundle.unwrap_or_else(test_store_bundle);
let vault_path = self.vault_path.unwrap_or_else(test_secret_store_path);
redirect_default_storage_root(&mut self.server_settings, &vault_path);
if !self.vault_entries.is_empty() {
let mut vault = Vault::load(vault_path.clone()).expect("test vault should load");
for (name, value) in &self.vault_entries {
@ -672,6 +674,25 @@ pub fn test_secret_store_path() -> PathBuf {
dir.join("secrets.json")
}
/// Keeps tests off the developer's real `~/.fabro/storage`.
///
/// Settings built for tests usually omit `[server.storage] root`, which
/// resolves to the production default. Handlers that walk that tree — `df`,
/// `system/resources`, `prune` — then read whatever runs and scratch
/// directories the machine happens to have, making tests slow and
/// machine-dependent, and letting run-creating tests write there.
///
/// Only settings still carrying the production default are redirected; a test
/// that chose its own root keeps it.
fn redirect_default_storage_root(settings: &mut ServerSettings, vault_path: &Path) {
if Path::new(&settings.server.storage.root) != default_storage_dir() {
return;
}
let root = vault_path.with_file_name("storage");
std::fs::create_dir_all(&root).expect("test storage root should be creatable");
settings.server.storage.root = root.display().to_string();
}
#[must_use]
pub fn test_auth_mode() -> AuthMode {
AuthMode::Enabled(ConfiguredAuth {