From 886173f5ad713c1e4f0a352bdc32905450c6f288 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sat, 2 May 2026 13:02:41 -0400 Subject: [PATCH] refactor(server): gate test app state helpers Move test-only app state constructors into the gated test_support module so release builds do not expose or link fixture helpers and secrets. --- lib/crates/fabro-cli/Cargo.toml | 1 + .../tests/it/support/auth_harness.rs | 8 +- lib/crates/fabro-server/Cargo.toml | 8 + lib/crates/fabro-server/src/auth/cli_flow.rs | 3 +- lib/crates/fabro-server/src/auth/translate.rs | 2 +- lib/crates/fabro-server/src/canonical_host.rs | 6 +- lib/crates/fabro-server/src/lib.rs | 1 + .../fabro-server/src/principal_middleware.rs | 14 +- lib/crates/fabro-server/src/run_manifest.rs | 6 +- lib/crates/fabro-server/src/server.rs | 541 +++--------------- lib/crates/fabro-server/src/test_support.rs | 369 +++++++++++- lib/crates/fabro-server/src/web_auth.rs | 8 +- .../tests/it/api/cli_auth_token.rs | 7 +- .../fabro-server/tests/it/api/routing.rs | 34 +- .../fabro-server/tests/it/api/run_files.rs | 4 +- .../fabro-server/tests/it/api/settings.rs | 4 +- lib/crates/fabro-server/tests/it/api/tcp.rs | 6 +- lib/crates/fabro-server/tests/it/helpers.rs | 17 +- .../tests/it/openapi_conformance.rs | 4 +- .../tests/it/scenario/lifecycle.rs | 11 +- 20 files changed, 531 insertions(+), 523 deletions(-) diff --git a/lib/crates/fabro-cli/Cargo.toml b/lib/crates/fabro-cli/Cargo.toml index 9055d55f6..d727f4aff 100644 --- a/lib/crates/fabro-cli/Cargo.toml +++ b/lib/crates/fabro-cli/Cargo.toml @@ -111,6 +111,7 @@ chrono = { workspace = true } [dev-dependencies] assert_cmd = "2" +fabro-server = { path = "../fabro-server", features = ["test-support"] } insta = { workspace = true, features = ["filters"] } paste = "1" predicates = "3" diff --git a/lib/crates/fabro-cli/tests/it/support/auth_harness.rs b/lib/crates/fabro-cli/tests/it/support/auth_harness.rs index 881df4a87..83ca6e64e 100644 --- a/lib/crates/fabro-cli/tests/it/support/auth_harness.rs +++ b/lib/crates/fabro-cli/tests/it/support/auth_harness.rs @@ -23,10 +23,8 @@ use fabro_config::{RunLayer, ServerSettingsBuilder}; use fabro_server::auth::GithubEndpoints; use fabro_server::ip_allowlist::IpAllowlistConfig; use fabro_server::jwt_auth::resolve_auth_mode_with_lookup; -use fabro_server::server::{ - RouterOptions, build_router_with_options, - create_app_state_with_runtime_settings_and_env_lookup_and_server_secret_env, -}; +use fabro_server::server::{RouterOptions, build_router_with_options}; +use fabro_server::test_support::test_app_state_with_runtime_settings_and_env_lookup_and_server_secret_env; use fabro_test::{GitHubAppState, TestContext, apply_test_isolation}; use serde_json::Value; use tokio::net::TcpListener; @@ -92,7 +90,7 @@ impl RealAuthHarness { if let Some(token) = dev_token.clone() { secrets.insert("FABRO_DEV_TOKEN".to_string(), token); } - let state = create_app_state_with_runtime_settings_and_env_lookup_and_server_secret_env( + let state = test_app_state_with_runtime_settings_and_env_lookup_and_server_secret_env( settings, RunLayer::default(), 5, diff --git a/lib/crates/fabro-server/Cargo.toml b/lib/crates/fabro-server/Cargo.toml index 45ad80746..11451e094 100644 --- a/lib/crates/fabro-server/Cargo.toml +++ b/lib/crates/fabro-server/Cargo.toml @@ -9,6 +9,14 @@ description = "HTTP server for Fabro pipelines" [lib] doctest = false +[features] +test-support = [] + +[[test]] +name = "it" +path = "tests/it/main.rs" +required-features = ["test-support"] + [lints] workspace = true diff --git a/lib/crates/fabro-server/src/auth/cli_flow.rs b/lib/crates/fabro-server/src/auth/cli_flow.rs index 03eb12600..2d5b48513 100644 --- a/lib/crates/fabro-server/src/auth/cli_flow.rs +++ b/lib/crates/fabro-server/src/auth/cli_flow.rs @@ -1243,7 +1243,6 @@ mod tests { use crate::auth::{self, AuthCode, AuthErrorCode, RefreshToken}; use crate::jwt_auth::{AuthMode, ConfiguredAuth}; use crate::principal_middleware::{AuthStatus, RequestAuthContext}; - use crate::server; use crate::web_auth::SessionCookie; fn test_cookie_key() -> Key { @@ -1303,7 +1302,7 @@ client_id = "github-client-id" settings: fabro_types::ServerSettings, auth_mode: AuthMode, ) -> (axum::Router, Arc) { - let state = server::create_test_app_state_with_runtime_settings_and_session_key( + let state = crate::test_support::test_app_state_with_runtime_settings_and_session_key( settings, RunLayer::default(), Some("cli-flow-test-key-material-0123456789"), diff --git a/lib/crates/fabro-server/src/auth/translate.rs b/lib/crates/fabro-server/src/auth/translate.rs index 48b6d272d..bf8361a14 100644 --- a/lib/crates/fabro-server/src/auth/translate.rs +++ b/lib/crates/fabro-server/src/auth/translate.rs @@ -219,7 +219,7 @@ methods = ["dev-token"] } fn test_state() -> Arc { - server::create_test_app_state_with_runtime_settings_and_session_key( + crate::test_support::test_app_state_with_runtime_settings_and_session_key( test_server_settings(), RunLayer::default(), Some(SESSION_SECRET), diff --git a/lib/crates/fabro-server/src/canonical_host.rs b/lib/crates/fabro-server/src/canonical_host.rs index 60471e0a4..41d2e5a5b 100644 --- a/lib/crates/fabro-server/src/canonical_host.rs +++ b/lib/crates/fabro-server/src/canonical_host.rs @@ -166,7 +166,7 @@ mod tests { use super::*; use crate::github_webhooks::WEBHOOK_ROUTE; - use crate::server::{self, AppState}; + use crate::server::AppState; macro_rules! assert_status { ($response:expr, $expected:expr) => { @@ -248,7 +248,7 @@ url = "{web_url}" } fn state_with_web_url(web_url: &str) -> Arc { - server::create_app_state_with_options( + crate::test_support::test_app_state_with_options( settings_with_web_url(web_url), RunLayer::default(), 5, @@ -256,7 +256,7 @@ url = "{web_url}" } fn invalid_canonical_origin_state() -> Arc { - server::create_app_state_with_env_lookup( + crate::test_support::test_app_state_with_env_lookup( settings_with_web_url("{{ env.FABRO_WEB_URL }}"), RunLayer::default(), 5, diff --git a/lib/crates/fabro-server/src/lib.rs b/lib/crates/fabro-server/src/lib.rs index d9ff230bd..1a86e08a3 100644 --- a/lib/crates/fabro-server/src/lib.rs +++ b/lib/crates/fabro-server/src/lib.rs @@ -38,6 +38,7 @@ mod server_secrets; mod spawn_env; mod startup; pub mod static_files; +#[cfg(any(test, feature = "test-support"))] pub mod test_support; pub mod web_auth; mod worker_token; diff --git a/lib/crates/fabro-server/src/principal_middleware.rs b/lib/crates/fabro-server/src/principal_middleware.rs index b2d3afb95..e8971831c 100644 --- a/lib/crates/fabro-server/src/principal_middleware.rs +++ b/lib/crates/fabro-server/src/principal_middleware.rs @@ -508,14 +508,14 @@ mod tests { } fn classify_token(token: Option<&str>) -> RequestAuthContext { - let state = crate::server::create_app_state(); + let state = crate::test_support::test_app_state(); let request = request_with_bearer(token, auth_mode_for_state(state.as_ref())); classify_request(&request, state.as_ref()) } #[test] fn classifies_valid_user_jwt_as_user_principal() { - let state = crate::server::create_app_state(); + let state = crate::test_support::test_app_state(); let token = issue_user_token(state.as_ref(), Duration::minutes(10)); let request = request_with_bearer(Some(&token), auth_mode_for_state(state.as_ref())); @@ -528,7 +528,7 @@ mod tests { #[test] fn classifies_expired_user_jwt_as_expired() { - let state = crate::server::create_app_state(); + let state = crate::test_support::test_app_state(); let token = issue_user_token(state.as_ref(), Duration::seconds(-60)); let request = request_with_bearer(Some(&token), auth_mode_for_state(state.as_ref())); @@ -554,7 +554,7 @@ mod tests { #[test] fn routes_worker_kid_to_worker_verifier() { - let state = crate::server::create_app_state(); + let state = crate::test_support::test_app_state(); let run_id = RunId::new(); let token = issue_worker_token(state.worker_token_keys(), &run_id).unwrap(); let request = request_with_bearer(Some(&token), auth_mode_for_state(state.as_ref())); @@ -567,7 +567,7 @@ mod tests { #[test] fn classifies_expired_worker_jwt_as_expired_not_invalid() { - let state = crate::server::create_app_state(); + let state = crate::test_support::test_app_state(); let token = issue_worker_claims(state.as_ref(), RunId::new(), 2, WORKER_TOKEN_SCOPE); let request = request_with_bearer(Some(&token), auth_mode_for_state(state.as_ref())); @@ -582,7 +582,7 @@ mod tests { #[test] fn classifies_invalid_worker_jwt_signature_as_invalid() { - let state = crate::server::create_app_state(); + let state = crate::test_support::test_app_state(); let token = issue_worker_claims_with_secret( b"other-principal-middleware-secret-0001", RunId::new(), @@ -602,7 +602,7 @@ mod tests { #[test] fn classifies_wrong_scope_worker_jwt_as_invalid() { - let state = crate::server::create_app_state(); + let state = crate::test_support::test_app_state(); let token = issue_worker_claims(state.as_ref(), RunId::new(), u64::MAX / 2, "wrong:scope"); let request = request_with_bearer(Some(&token), auth_mode_for_state(state.as_ref())); diff --git a/lib/crates/fabro-server/src/run_manifest.rs b/lib/crates/fabro-server/src/run_manifest.rs index 6621f49e8..b60b2e0ac 100644 --- a/lib/crates/fabro-server/src/run_manifest.rs +++ b/lib/crates/fabro-server/src/run_manifest.rs @@ -1722,7 +1722,7 @@ app_id = "fixture-app-id" #[tokio::test] async fn invalid_preflight_returns_diagnostics_without_runtime_checks() { - let state = crate::server::create_app_state(); + let state = crate::test_support::test_app_state(); let prepared = prepare_manifest( &manifest_run_defaults(Some(&default_settings_fixture())), &invalid_manifest(), @@ -1746,7 +1746,7 @@ app_id = "fixture-app-id" #[tokio::test] async fn preflight_allows_pull_request_enabled_without_github_credentials() { - let state = crate::server::create_app_state(); + let state = crate::test_support::test_app_state(); let mut manifest = minimal_manifest(); manifest.configs.push(types::ManifestConfig { path: Some("/tmp/project/.fabro/project.toml".to_string()), @@ -1790,7 +1790,7 @@ provider = "local" #[tokio::test] async fn preflight_daytona_without_github_credentials_returns_report() { - let state = crate::server::create_app_state(); + let state = crate::test_support::test_app_state(); let mut manifest = minimal_manifest(); manifest.configs.push(types::ManifestConfig { path: Some("/tmp/project/.fabro/project.toml".to_string()), diff --git a/lib/crates/fabro-server/src/server.rs b/lib/crates/fabro-server/src/server.rs index 64915572a..dbda3faf2 100644 --- a/lib/crates/fabro-server/src/server.rs +++ b/lib/crates/fabro-server/src/server.rs @@ -41,8 +41,10 @@ pub use fabro_api::types::{ use fabro_auth::{ CredentialSource, VaultCredentialSource, auth_issue_message, parse_credential_secret, }; +#[cfg(test)] +use fabro_config::RunSettingsBuilder; use fabro_config::daemon::ServerDaemon; -use fabro_config::{RunLayer, RunSettingsBuilder, ServerSettingsBuilder, Storage, envfile}; +use fabro_config::{RunLayer, Storage}; use fabro_interview::{ Answer, AnswerSubmission, ControlInterviewer, Interviewer, Question, WorkerControlEnvelope, }; @@ -94,7 +96,6 @@ use fabro_workflow::run_lookup::{ }; use fabro_workflow::run_status::{FailureReason, RunStatus, SuccessReason}; use fabro_workflow::{Error as WorkflowError, operations, pull_request}; -use object_store::memory::InMemory as MemoryObjectStore; use sha2::{Digest, Sha256}; use tempfile::NamedTempFile; use tokio::fs; @@ -871,7 +872,7 @@ fn resolve_interp_string(value: &InterpString) -> anyhow::Result { clippy::disallowed_methods, reason = "Server state owns process-env lookup facades for interpolation and vault fallbacks." )] -fn process_env_var(name: &str) -> Option { +pub(crate) fn process_env_var(name: &str) -> Option { std::env::var(name).ok() } @@ -1801,6 +1802,7 @@ fn build_prune_plan( }) } +#[cfg(test)] fn resolve_manifest_run_settings( manifest_run_defaults: &RunLayer, ) -> std::result::Result { @@ -1808,18 +1810,6 @@ fn resolve_manifest_run_settings( .map_err(|err| SharedError::new(anyhow::Error::new(err))) } -fn default_test_server_settings() -> ServerSettings { - ServerSettingsBuilder::from_toml( - r#" -_version = 1 - -[server.auth] -methods = ["dev-token"] -"#, - ) - .expect("default test server settings should resolve") -} - fn system_sandbox_provider( manifest_run_settings: &std::result::Result, ) -> String { @@ -2536,342 +2526,6 @@ async fn get_run_billing( (StatusCode::OK, Json(response)).into_response() } -/// Create an `AppState` with default settings. -pub fn create_app_state() -> Arc { - create_app_state_with_options(default_test_server_settings(), RunLayer::default(), 5) -} - -#[doc(hidden)] -pub fn create_app_state_with_registry_factory( - registry_factory_override: impl Fn(Arc) -> HandlerRegistry + Send + Sync + 'static, -) -> Arc { - create_app_state_with_settings_and_registry_factory( - default_test_server_settings(), - RunLayer::default(), - registry_factory_override, - ) -} - -#[doc(hidden)] -pub fn create_app_state_with_settings_and_registry_factory( - server_settings: ServerSettings, - manifest_run_defaults: RunLayer, - registry_factory_override: impl Fn(Arc) -> HandlerRegistry + Send + Sync + 'static, -) -> Arc { - create_app_state_with_options_and_registry_factory( - server_settings, - manifest_run_defaults, - 5, - registry_factory_override, - ) -} - -#[doc(hidden)] -pub fn create_app_state_with_options_and_registry_factory( - server_settings: ServerSettings, - manifest_run_defaults: RunLayer, - max_concurrent_runs: usize, - registry_factory_override: impl Fn(Arc) -> HandlerRegistry + Send + Sync + 'static, -) -> Arc { - create_app_state_with_runtime_settings_and_options_and_registry_factory( - server_settings, - manifest_run_defaults, - max_concurrent_runs, - registry_factory_override, - ) -} - -/// Create an `AppState` with the given settings and concurrency limit. -pub fn create_app_state_with_options( - server_settings: ServerSettings, - manifest_run_defaults: RunLayer, - max_concurrent_runs: usize, -) -> Arc { - create_app_state_with_runtime_settings_and_options( - server_settings, - manifest_run_defaults, - max_concurrent_runs, - ) -} - -fn resolved_runtime_settings_for_tests( - server_settings: ServerSettings, - manifest_run_defaults: RunLayer, -) -> ResolvedAppStateSettings { - ResolvedAppStateSettings { - manifest_run_settings: resolve_manifest_run_settings(&manifest_run_defaults), - manifest_run_defaults, - server_settings, - } -} - -#[doc(hidden)] -pub fn create_app_state_with_runtime_settings_and_registry_factory( - server_settings: ServerSettings, - manifest_run_defaults: RunLayer, - registry_factory_override: impl Fn(Arc) -> HandlerRegistry + Send + Sync + 'static, -) -> Arc { - create_app_state_with_runtime_settings_and_options_and_registry_factory( - server_settings, - manifest_run_defaults, - 5, - registry_factory_override, - ) -} - -#[doc(hidden)] -pub fn create_app_state_with_runtime_settings_and_options_and_registry_factory( - server_settings: ServerSettings, - manifest_run_defaults: RunLayer, - max_concurrent_runs: usize, - registry_factory_override: impl Fn(Arc) -> HandlerRegistry + Send + Sync + 'static, -) -> Arc { - let (store, artifact_store) = test_store_bundle(); - let vault_path = test_secret_store_path(); - let server_env_path = vault_path.with_file_name("server.env"); - let env_lookup = default_env_lookup(); - let mut config = AppStateConfig { - resolved_settings: resolved_runtime_settings_for_tests( - server_settings, - manifest_run_defaults, - ), - registry_factory_override: None, - max_concurrent_runs, - store, - artifact_store, - vault_path, - server_secrets: load_test_server_secrets(server_env_path, HashMap::new()), - env_lookup, - github_api_base_url: None, - http_client: Some(fabro_http::test_http_client().expect("test HTTP client should build")), - }; - config.registry_factory_override = Some(Box::new(registry_factory_override)); - build_app_state(config).expect("test app state should build") -} - -/// Create an `AppState` with dense runtime settings and a concurrency limit. -#[doc(hidden)] -pub fn create_app_state_with_runtime_settings_and_options( - server_settings: ServerSettings, - manifest_run_defaults: RunLayer, - max_concurrent_runs: usize, -) -> Arc { - create_app_state_with_runtime_settings_and_env_lookup_and_server_secret_env( - server_settings, - manifest_run_defaults, - max_concurrent_runs, - process_env_var, - &HashMap::new(), - ) -} - -#[doc(hidden)] -pub fn create_app_state_with_runtime_settings_and_env_lookup( - server_settings: ServerSettings, - manifest_run_defaults: RunLayer, - max_concurrent_runs: usize, - env_lookup: impl Fn(&str) -> Option + Send + Sync + 'static, -) -> Arc { - create_app_state_with_runtime_settings_and_env_lookup_and_server_secret_env( - server_settings, - manifest_run_defaults, - max_concurrent_runs, - env_lookup, - &HashMap::new(), - ) -} - -#[doc(hidden)] -pub fn create_app_state_with_runtime_settings_and_env_lookup_and_server_secret_env( - server_settings: ServerSettings, - manifest_run_defaults: RunLayer, - max_concurrent_runs: usize, - env_lookup: impl Fn(&str) -> Option + Send + Sync + 'static, - server_secret_env: &HashMap, -) -> Arc { - let (store, artifact_store) = test_store_bundle(); - let env_lookup: EnvLookup = Arc::new(env_lookup); - let vault_path = test_secret_store_path(); - let server_env_path = vault_path.with_file_name("server.env"); - build_app_state(AppStateConfig { - resolved_settings: resolved_runtime_settings_for_tests( - server_settings, - manifest_run_defaults, - ), - registry_factory_override: None, - max_concurrent_runs, - store, - artifact_store, - vault_path, - server_secrets: load_test_server_secrets(server_env_path, server_secret_env.clone()), - env_lookup, - github_api_base_url: None, - http_client: Some(fabro_http::test_http_client().expect("test HTTP client should build")), - }) - .expect("test app state should build") -} - -#[doc(hidden)] -pub fn create_app_state_with_env_lookup( - server_settings: ServerSettings, - manifest_run_defaults: RunLayer, - max_concurrent_runs: usize, - env_lookup: impl Fn(&str) -> Option + Send + Sync + 'static, -) -> Arc { - create_app_state_with_runtime_settings_and_env_lookup( - server_settings, - manifest_run_defaults, - max_concurrent_runs, - env_lookup, - ) -} - -#[doc(hidden)] -pub fn create_app_state_with_env_lookup_and_server_secret_env( - server_settings: ServerSettings, - manifest_run_defaults: RunLayer, - max_concurrent_runs: usize, - env_lookup: impl Fn(&str) -> Option + Send + Sync + 'static, - server_secret_env: &HashMap, -) -> Arc { - create_app_state_with_runtime_settings_and_env_lookup_and_server_secret_env( - server_settings, - manifest_run_defaults, - max_concurrent_runs, - env_lookup, - server_secret_env, - ) -} - -#[cfg(test)] -#[expect( - clippy::disallowed_methods, - reason = "test helper writes a fixture server.env with sync std::fs::write" -)] -pub(crate) fn create_test_app_state_with_runtime_settings_and_session_key( - server_settings: ServerSettings, - manifest_run_defaults: RunLayer, - session_secret: Option<&str>, -) -> Arc { - let vault_path = test_secret_store_path(); - let server_env_path = vault_path - .parent() - .expect("test secrets path should have parent") - .join("server.env"); - if let Some(session_secret) = session_secret { - std::fs::write( - &server_env_path, - format!("SESSION_SECRET={session_secret}\n"), - ) - .expect("test server env should be writable"); - } - let (store, artifact_store) = test_store_bundle(); - let env_lookup = default_env_lookup(); - build_app_state(AppStateConfig { - resolved_settings: resolved_runtime_settings_for_tests( - server_settings, - manifest_run_defaults, - ), - registry_factory_override: None, - max_concurrent_runs: 5, - store, - artifact_store, - vault_path, - server_secrets: load_test_server_secrets(server_env_path, HashMap::new()), - env_lookup, - github_api_base_url: None, - http_client: Some(fabro_http::test_http_client().expect("test HTTP client should build")), - }) - .expect("test app state should build") -} - -#[cfg(test)] -pub(crate) fn create_test_app_state_with_session_key( - server_settings: ServerSettings, - manifest_run_defaults: RunLayer, - session_secret: Option<&str>, -) -> Arc { - create_test_app_state_with_runtime_settings_and_session_key( - server_settings, - manifest_run_defaults, - session_secret, - ) -} - -pub fn create_app_state_with_store( - server_settings: ServerSettings, - manifest_run_defaults: RunLayer, - max_concurrent_runs: usize, - store: Arc, - artifact_store: ArtifactStore, -) -> Arc { - create_app_state_with_store_and_runtime_settings( - server_settings, - manifest_run_defaults, - max_concurrent_runs, - store, - artifact_store, - ) -} - -fn test_store_bundle() -> (Arc, ArtifactStore) { - let object_store: Arc = Arc::new(MemoryObjectStore::new()); - let store = Arc::new(fabro_store::Database::new( - Arc::clone(&object_store), - "", - Duration::from_millis(1), - None, - )); - let artifact_store = ArtifactStore::new(object_store, "artifacts"); - (store, artifact_store) -} - -#[doc(hidden)] -pub fn create_app_state_with_store_and_runtime_settings( - server_settings: ServerSettings, - manifest_run_defaults: RunLayer, - max_concurrent_runs: usize, - store: Arc, - artifact_store: ArtifactStore, -) -> Arc { - let vault_path = test_secret_store_path(); - let server_env_path = vault_path.with_file_name("server.env"); - build_app_state(AppStateConfig { - resolved_settings: resolved_runtime_settings_for_tests( - server_settings, - manifest_run_defaults, - ), - registry_factory_override: None, - max_concurrent_runs, - store, - artifact_store, - vault_path, - server_secrets: load_test_server_secrets(server_env_path, HashMap::new()), - env_lookup: default_env_lookup(), - github_api_base_url: None, - http_client: Some(fabro_http::test_http_client().expect("test HTTP client should build")), - }) - .expect("test app state should build") -} - -fn default_env_lookup() -> EnvLookup { - Arc::new(process_env_var) -} - -fn load_test_server_secrets(path: PathBuf, env: HashMap) -> ServerSecrets { - let mut env = env; - let file_has_session_secret = envfile::read_env_file(&path) - .ok() - .is_some_and(|entries| entries.contains_key(EnvVars::SESSION_SECRET)); - if !env.contains_key(EnvVars::SESSION_SECRET) && !file_has_session_secret { - env.insert( - EnvVars::SESSION_SECRET.to_string(), - "server-test-session-key-0123456789".to_string(), - ); - } - ServerSecrets::load(path, env).expect("test server secrets should load") -} - fn worker_token_keys_from_server_secrets( server_secrets: &ServerSecrets, ) -> anyhow::Result { @@ -2962,12 +2616,6 @@ pub(crate) fn build_app_state(config: AppStateConfig) -> anyhow::Result PathBuf { - let dir = std::env::temp_dir().join(format!("fabro-test-{}", Ulid::new())); - std::fs::create_dir_all(&dir).expect("test temp dir should be creatable"); - dir.join("secrets.json") -} - fn board_column(status: RunStatus) -> Option<&'static str> { match status { RunStatus::Submitted | RunStatus::Queued | RunStatus::Starting => Some("initializing"), @@ -8381,6 +8029,7 @@ mod tests { use super::*; use crate::github_webhooks::compute_signature; use crate::jwt_auth::{AuthMode, ConfiguredAuth}; + use crate::test_support::*; const MINIMAL_DOT: &str = r#"digraph Test { graph [goal="Test"] @@ -8418,7 +8067,7 @@ mod tests { } fn test_app_with() -> Router { - let state = create_app_state(); + let state = test_app_state(); crate::test_support::build_test_router_with_options( state, Arc::new(IpAllowlistConfig::default()), @@ -8438,7 +8087,7 @@ mod tests { crate::test_support::build_test_router(state) } - fn create_app_state_with_isolated_storage() -> Arc { + fn test_app_state_with_isolated_storage() -> Arc { let storage_dir = std::env::temp_dir().join(format!("fabro-server-test-{}", Ulid::new())); std::fs::create_dir_all(&storage_dir).expect("test storage dir should be creatable"); let source = format!( @@ -8454,7 +8103,7 @@ methods = ["dev-token"] storage_dir.display() ); - create_app_state_with_options( + test_app_state_with_options( server_settings_from_toml(&source), manifest_run_defaults_from_toml(&source), 5, @@ -8739,7 +8388,7 @@ methods = ["dev-token"] )] fn webhook_test_app(auth_mode: AuthMode) -> Router { let secret = TEST_WEBHOOK_SECRET.to_string(); - let state = create_app_state_with_env_lookup_and_server_secret_env( + let state = test_app_state_with_env_lookup_and_server_secret_env( default_test_server_settings(), RunLayer::default(), 5, @@ -8798,7 +8447,7 @@ methods = ["dev-token"] } fn jwt_auth_state() -> Arc { - create_test_app_state_with_session_key( + test_app_state_with_session_key( default_test_server_settings(), RunLayer::default(), Some(TEST_SESSION_SECRET), @@ -8882,7 +8531,7 @@ url = "{url}" } fn canonical_host_test_app() -> Router { - let state = create_app_state_with_options( + let state = test_app_state_with_options( canonical_origin_settings("http://127.0.0.1:32276"), RunLayer::default(), 5, @@ -8944,7 +8593,7 @@ url = "{url}" "ftp://fabro.example.com", "http://0.0.0.0:32276", ] { - let state = create_app_state_with_env_lookup( + let state = test_app_state_with_env_lookup( canonical_origin_settings("http://valid.example.com"), RunLayer::default(), 5, @@ -8981,7 +8630,7 @@ url = "{{ env.FABRO_WEB_URL }}" #[test] fn replace_settings_updates_layer_and_typed_server_settings() { - let state = create_app_state_with_options( + let state = test_app_state_with_options( server_settings_from_toml( r#" _version = 1 @@ -9058,7 +8707,7 @@ root = "/srv/new" #[test] fn replace_settings_caches_invalid_manifest_run_settings_tolerantly() { - let state = create_app_state_with_options( + let state = test_app_state_with_options( server_settings_from_toml( r#" _version = 1 @@ -9198,7 +8847,7 @@ provider = "invalid-provider" #[tokio::test] async fn create_secret_stores_file_secret_and_excludes_it_from_snapshot() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let req = Request::builder() .method("POST") @@ -9299,7 +8948,7 @@ provider = "invalid-provider" #[tokio::test] async fn create_secret_stores_valid_credential_entries() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let credential = fabro_auth::AuthCredential { provider: Provider::OpenAi, @@ -9348,7 +8997,7 @@ provider = "invalid-provider" #[tokio::test] async fn resolve_llm_client_reads_openai_codex_credential_from_vault() { - let state = create_app_state_with_env_lookup( + let state = test_app_state_with_env_lookup( default_test_server_settings(), RunLayer::default(), 5, @@ -9407,7 +9056,7 @@ provider = "invalid-provider" #[tokio::test] async fn llm_source_configured_providers_reads_openai_codex_from_vault() { - let state = create_app_state_with_env_lookup( + let state = test_app_state_with_env_lookup( default_test_server_settings(), RunLayer::default(), 5, @@ -9445,7 +9094,7 @@ provider = "invalid-provider" }) .await; let base_url = server.url("/v1"); - let state = create_app_state_with_env_lookup( + let state = test_app_state_with_env_lookup( default_test_server_settings(), RunLayer::default(), 5, @@ -9495,7 +9144,7 @@ provider = "invalid-provider" #[tokio::test] async fn list_secrets_includes_credential_metadata() { - let state = create_app_state(); + let state = test_app_state(); { let mut vault = state.vault.write().await; vault @@ -9534,7 +9183,7 @@ provider = "invalid-provider" #[tokio::test] async fn create_secret_rejects_invalid_credential_json() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(state); let req = Request::builder() @@ -9557,7 +9206,7 @@ provider = "invalid-provider" #[tokio::test] async fn create_secret_rejects_wrong_credential_name() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(state); let req = Request::builder() @@ -9597,7 +9246,7 @@ provider = "invalid-provider" #[tokio::test] async fn delete_secret_by_name_removes_file_secret() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let create_req = Request::builder() @@ -10011,7 +9660,7 @@ allowed_usernames = ["octocat"] for (key, value) in extra_server_secrets { server_secret_env.insert((*key).to_string(), (*value).to_string()); } - create_app_state_with_env_lookup_and_server_secret_env( + test_app_state_with_env_lookup_and_server_secret_env( server_settings_from_toml(&source), manifest_run_defaults_from_toml(&source), 5, @@ -10228,7 +9877,7 @@ allowed_usernames = ["octocat"] #[tokio::test] async fn list_run_stages_projects_retrying_until_completion() { - let state = create_app_state_with_isolated_storage(); + let state = test_app_state_with_isolated_storage(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = RunId::new(); @@ -10645,7 +10294,7 @@ strategy = "token" #[tokio::test] async fn test_model_alias_returns_canonical_model_id() { - let state = create_app_state_with_env_lookup( + let state = test_app_state_with_env_lookup( default_test_server_settings(), RunLayer::default(), 5, @@ -10668,7 +10317,7 @@ strategy = "token" #[tokio::test] async fn test_model_invalid_mode_returns_400() { - let state = create_app_state_with_env_lookup( + let state = test_app_state_with_env_lookup( default_test_server_settings(), RunLayer::default(), 5, @@ -10735,7 +10384,7 @@ strategy = "token" #[tokio::test] async fn list_models_marks_configured_true_when_provider_has_credential_material() { - let state = create_app_state_with_env_lookup( + let state = test_app_state_with_env_lookup( default_test_server_settings(), RunLayer::default(), 5, @@ -10771,7 +10420,7 @@ strategy = "token" #[tokio::test] async fn list_models_marks_configured_false_when_no_credential_material() { - let state = create_app_state_with_env_lookup( + let state = test_app_state_with_env_lookup( default_test_server_settings(), RunLayer::default(), 5, @@ -10832,7 +10481,7 @@ client_id = "Iv1.testclient" slug = "fabro" "#; let app = build_router( - create_test_app_state_with_session_key( + test_app_state_with_session_key( server_settings_from_toml(source), manifest_run_defaults_from_toml(source), Some("github-redirect-test-key-0123456789"), @@ -10947,7 +10596,7 @@ slug = "fabro" #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_run_status_returns_status() { - let state = create_app_state(); + let state = test_app_state(); let app = test_app_with_scheduler(state); let run_id = create_and_start_run(&app, MINIMAL_DOT).await; @@ -11136,7 +10785,7 @@ slug = "fabro" #[tokio::test] async fn get_questions_returns_empty_list() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); // Start a run @@ -11184,7 +10833,7 @@ slug = "fabro" #[tokio::test] async fn submit_pending_interview_answer_rejects_invalid_answer_shape() { - let state = create_app_state(); + let state = test_app_state(); let pending = LoadedPendingInterview { run_id: fixtures::RUN_1, qid: "q-1".to_string(), @@ -11234,7 +10883,7 @@ slug = "fabro" #[tokio::test] async fn get_run_state_returns_projection() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let req = Request::builder() @@ -11261,7 +10910,7 @@ slug = "fabro" #[tokio::test] async fn get_run_logs_returns_per_run_log_file() { - let state = create_app_state_with_isolated_storage(); + let state = test_app_state_with_isolated_storage(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = RunId::new(); create_durable_run_with_events(&state, run_id, &[workflow_event::Event::RunSubmitted { @@ -11299,7 +10948,7 @@ slug = "fabro" #[tokio::test] async fn get_run_logs_returns_not_found_for_missing_run() { - let state = create_app_state_with_isolated_storage(); + let state = test_app_state_with_isolated_storage(); let app = crate::test_support::build_test_router(state); let missing_run_id = RunId::new(); @@ -11315,7 +10964,7 @@ slug = "fabro" #[tokio::test] async fn get_run_logs_returns_not_found_when_log_file_is_missing() { - let state = create_app_state_with_isolated_storage(); + let state = test_app_state_with_isolated_storage(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = RunId::new(); create_durable_run_with_events(&state, run_id, &[workflow_event::Event::RunSubmitted { @@ -11335,7 +10984,7 @@ slug = "fabro" #[tokio::test] async fn get_run_stage_command_log_returns_scratch_slice() { - let state = create_app_state_with_isolated_storage(); + let state = test_app_state_with_isolated_storage(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = RunId::new(); let stage_id = StageId::new("script_node", 1); @@ -11396,7 +11045,7 @@ slug = "fabro" #[tokio::test] async fn get_run_stage_command_log_returns_cas_slice() { - let state = create_app_state_with_isolated_storage(); + let state = test_app_state_with_isolated_storage(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = RunId::new(); let run_store = state.store.create_run(&run_id).await.unwrap(); @@ -11466,7 +11115,7 @@ slug = "fabro" #[tokio::test] async fn get_run_stage_command_log_prefers_scratch_when_cas_ref_exists() { - let state = create_app_state_with_isolated_storage(); + let state = test_app_state_with_isolated_storage(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = RunId::new(); let stage_id = StageId::new("script_node", 1); @@ -11547,7 +11196,7 @@ slug = "fabro" #[tokio::test] async fn get_run_stage_command_log_returns_not_found_for_missing_stage() { - let state = create_app_state_with_isolated_storage(); + let state = test_app_state_with_isolated_storage(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = RunId::new(); create_durable_run_with_events(&state, run_id, &[workflow_event::Event::RunSubmitted { @@ -11631,7 +11280,7 @@ slug = "fabro" #[tokio::test] async fn get_run_pull_request_returns_not_found_when_record_missing() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = create_run(&app, MINIMAL_DOT).await; @@ -12255,7 +11904,7 @@ slug = "fabro" #[tokio::test] async fn get_run_state_exposes_pending_interviews() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = fixtures::RUN_1; @@ -12307,7 +11956,7 @@ slug = "fabro" #[tokio::test] async fn get_run_state_includes_provenance_from_user_agent() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let req = Request::builder() @@ -12357,7 +12006,7 @@ slug = "fabro" const DEV_TOKEN: &str = "fabro_dev_abababababababababababababababababababababababababababababababab"; - let state = create_test_app_state_with_session_key( + let state = test_app_state_with_session_key( default_test_server_settings(), RunLayer::default(), Some("server-test-session-key-0123456789"), @@ -12433,7 +12082,7 @@ slug = "fabro" #[tokio::test] async fn create_run_persists_manifest_and_definition_blobs_without_bundle_file() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let raw_manifest = serde_json::to_string_pretty(&minimal_manifest_json(MINIMAL_DOT)).unwrap(); @@ -12492,7 +12141,7 @@ slug = "fabro" #[tokio::test] async fn list_run_events_returns_paginated_json() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let req = Request::builder() @@ -12520,7 +12169,7 @@ slug = "fabro" #[tokio::test] async fn append_run_event_rejects_run_id_mismatch() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let req = Request::builder() @@ -12556,7 +12205,7 @@ slug = "fabro" #[tokio::test] async fn append_run_event_rejects_reserved_archive_event() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = create_run(&app, MINIMAL_DOT).await; @@ -12590,7 +12239,7 @@ slug = "fabro" #[tokio::test] async fn get_checkpoint_returns_null_initially() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); // Start a run @@ -12618,7 +12267,7 @@ slug = "fabro" #[tokio::test] async fn write_and_read_run_blob_round_trip() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let req = Request::builder() @@ -12654,7 +12303,7 @@ slug = "fabro" #[tokio::test] async fn stage_artifacts_round_trip() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = create_run(&app, MINIMAL_DOT).await; @@ -12706,7 +12355,7 @@ slug = "fabro" #[tokio::test] async fn stage_artifacts_keep_same_filename_per_retry() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = create_run(&app, MINIMAL_DOT).await; @@ -12751,7 +12400,7 @@ slug = "fabro" #[tokio::test] async fn create_run_persists_run_spec() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = create_run(&app, MINIMAL_DOT) @@ -12772,7 +12421,7 @@ slug = "fabro" #[tokio::test] async fn stage_artifact_upload_rejects_invalid_filename() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = create_run(&app, MINIMAL_DOT).await; @@ -13143,7 +12792,7 @@ slug = "fabro" #[tokio::test] async fn stage_artifacts_multipart_round_trip() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = create_run(&app, MINIMAL_DOT).await; @@ -13215,7 +12864,7 @@ slug = "fabro" #[tokio::test] async fn stage_artifacts_multipart_requires_manifest_first() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = create_run(&app, MINIMAL_DOT).await; @@ -13241,7 +12890,7 @@ slug = "fabro" #[tokio::test] async fn create_run_returns_submitted() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let req = Request::builder() @@ -13258,7 +12907,7 @@ slug = "fabro" #[tokio::test] async fn start_run_transitions_to_queued() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); // Create a run @@ -13297,7 +12946,7 @@ slug = "fabro" #[tokio::test] async fn start_run_conflict_when_not_submitted() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); // Create a run @@ -13331,7 +12980,7 @@ slug = "fabro" #[tokio::test] async fn cancel_run_succeeds() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = create_and_start_run(&app, MINIMAL_DOT) @@ -13372,7 +13021,7 @@ slug = "fabro" #[tokio::test] async fn get_graph_returns_svg() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); // Start a run @@ -13433,7 +13082,7 @@ slug = "fabro" #[tokio::test] async fn get_graph_source_returns_dot() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let req = Request::builder() @@ -13619,7 +13268,7 @@ slug = "fabro" #[tokio::test] async fn list_runs_returns_started_run() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); // List should be empty initially @@ -13670,7 +13319,7 @@ slug = "fabro" #[tokio::test] async fn archive_and_unarchive_updates_listing_visibility() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = fixtures::RUN_1; @@ -13809,7 +13458,7 @@ slug = "fabro" #[tokio::test] async fn delete_run_removes_durable_run() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let req = Request::builder() @@ -13842,7 +13491,7 @@ slug = "fabro" #[tokio::test] async fn delete_active_run_requires_force() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let req = Request::builder() @@ -13883,7 +13532,7 @@ slug = "fabro" #[tokio::test] async fn delete_active_run_force_succeeds() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let req = Request::builder() @@ -13916,7 +13565,7 @@ slug = "fabro" #[tokio::test] async fn get_aggregate_billing_returns_zeros_initially() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let req = Request::builder() @@ -13937,7 +13586,7 @@ slug = "fabro" #[tokio::test] async fn post_runs_returns_submitted_status() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(state); let req = Request::builder() @@ -14005,7 +13654,7 @@ url = "http://api.example.test" [server.logging] level = "debug" "#; - let state = create_app_state_with_options( + let state = test_app_state_with_options( server_settings_from_toml(source), manifest_run_defaults_from_toml(source), 5, @@ -14075,7 +13724,7 @@ level = "debug" #[tokio::test] async fn cancel_queued_run_succeeds() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = create_and_start_run(&app, MINIMAL_DOT) @@ -14139,7 +13788,7 @@ level = "debug" #[tokio::test] async fn cancel_run_overwrites_pending_pause_request() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id_str = create_and_start_run(&app, MINIMAL_DOT).await; let run_id = run_id_str.parse::().unwrap(); @@ -14169,7 +13818,7 @@ level = "debug" #[tokio::test] async fn pause_run_rejects_when_control_is_already_pending() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id_str = create_and_start_run(&app, MINIMAL_DOT).await; let run_id = run_id_str.parse::().unwrap(); @@ -14198,7 +13847,7 @@ level = "debug" #[tokio::test] async fn pause_run_sets_pending_control_on_board_response() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id_str = create_and_start_run(&app, MINIMAL_DOT).await; let run_id = run_id_str.parse::().unwrap(); @@ -14252,7 +13901,7 @@ level = "debug" #[tokio::test] async fn pause_run_immediately_pauses_blocked_run() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id_str = create_and_start_run(&app, MINIMAL_DOT).await; let run_id = run_id_str.parse::().unwrap(); @@ -14317,7 +13966,7 @@ level = "debug" #[tokio::test] async fn unpause_run_sets_pending_control() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id_str = create_and_start_run(&app, MINIMAL_DOT).await; let run_id = run_id_str.parse::().unwrap(); @@ -14346,7 +13995,7 @@ level = "debug" #[tokio::test] async fn unpause_run_returns_blocked_when_human_gate_is_still_unresolved() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id_str = create_and_start_run(&app, MINIMAL_DOT).await; let run_id = run_id_str.parse::().unwrap(); @@ -14421,7 +14070,7 @@ level = "debug" #[tokio::test] async fn startup_reconciliation_marks_inflight_runs_terminal() { - let state = create_app_state(); + let state = test_app_state(); create_durable_run_with_events(&state, fixtures::RUN_1, &[ workflow_event::Event::RunSubmitted { @@ -14492,7 +14141,7 @@ level = "debug" #[cfg(unix)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn shutdown_active_workers_terminates_process_groups() { - let state = create_app_state(); + let state = test_app_state(); let run_id = fixtures::RUN_4; create_durable_run_with_events(&state, run_id, &[ @@ -14577,7 +14226,7 @@ timeout = "30s" [run.sandbox] provider = "local" "#; - let state = create_app_state_with_settings_and_registry_factory( + let state = test_app_state_with_settings_and_registry_factory( server_settings_from_toml(source), manifest_run_defaults_from_toml(source), |interviewer| fabro_workflow::handler::default_registry(interviewer, || None), @@ -14677,7 +14326,7 @@ provider = "local" reason = "This test intentionally blocks inside a sync registry factory to simulate slow startup before cancellation." )] async fn cancel_before_run_transitions_to_running_returns_empty_attach_stream() { - let state = create_app_state_with_registry_factory(|interviewer| { + let state = test_app_state_with_registry_factory(|interviewer| { std::thread::sleep(std::time::Duration::from_millis(200)); fabro_workflow::handler::default_registry(interviewer, || None) }); @@ -14714,7 +14363,7 @@ provider = "local" #[tokio::test] async fn queue_position_reported_for_queued_runs() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); // Create and start two runs (no scheduler, both stay queued) @@ -14734,7 +14383,7 @@ provider = "local" #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn concurrency_limit_respected() { let state = - create_app_state_with_options(default_test_server_settings(), RunLayer::default(), 1); + test_app_state_with_options(default_test_server_settings(), RunLayer::default(), 1); let app = test_app_with_scheduler(Arc::clone(&state)); // Create and start two runs with max_concurrent_runs=1 @@ -14767,7 +14416,7 @@ provider = "local" #[tokio::test] async fn submit_answer_to_queued_run_returns_conflict() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(state); let req = Request::builder() @@ -14812,7 +14461,7 @@ provider = "local" #[tokio::test] async fn demo_boards_runs_returns_run_list_items() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(state); let req = Request::builder() .method("GET") @@ -14838,7 +14487,7 @@ provider = "local" #[tokio::test] async fn demo_get_run_returns_run_summary_shape() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(state); let run_id = RunId::with_timestamp( "2026-03-06T14:30:00Z" @@ -14870,7 +14519,7 @@ provider = "local" #[tokio::test] async fn demo_get_run_returns_404_for_unknown_run() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(state); let req = Request::builder() .method("GET") @@ -14884,7 +14533,7 @@ provider = "local" #[tokio::test] async fn boards_runs_returns_run_list_items_with_board_columns() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = create_and_start_run(&app, MINIMAL_DOT).await; @@ -14924,7 +14573,7 @@ provider = "local" #[tokio::test] async fn boards_runs_excludes_removing_status() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = fixtures::RUN_1; @@ -14955,7 +14604,7 @@ provider = "local" #[tokio::test] async fn get_run_exposes_canonical_operator_statuses() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let succeeded_id = fixtures::RUN_1; @@ -15027,7 +14676,7 @@ provider = "local" #[tokio::test] async fn boards_runs_maps_statuses_to_columns() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let paused_id = fixtures::RUN_1; @@ -15178,7 +14827,7 @@ provider = "local" #[tokio::test] async fn boards_runs_includes_live_board_metadata_from_run_state() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let run_id = create_and_start_run(&app, MINIMAL_DOT) .await @@ -15246,7 +14895,7 @@ provider = "local" #[tokio::test] async fn boards_runs_page_limit_preserves_metadata_for_paged_items() { - let state = create_app_state(); + let state = test_app_state(); let app = crate::test_support::build_test_router(Arc::clone(&state)); let first_run_id = create_and_start_run(&app, MINIMAL_DOT) diff --git a/lib/crates/fabro-server/src/test_support.rs b/lib/crates/fabro-server/src/test_support.rs index ed764bc62..0b286fd8c 100644 --- a/lib/crates/fabro-server/src/test_support.rs +++ b/lib/crates/fabro-server/src/test_support.rs @@ -1,6 +1,9 @@ +use std::collections::HashMap; +use std::path::PathBuf; #[cfg(test)] use std::sync::Mutex; use std::sync::{Arc, OnceLock}; +use std::time::Duration; use axum::extract::Request; #[cfg(test)] @@ -9,23 +12,376 @@ use axum::http::{HeaderValue, header}; use axum::middleware::Next; use axum::response::Response; use axum::{Router, middleware}; -use chrono::Duration; +use chrono::Duration as ChronoDuration; +use fabro_config::{RunLayer, RunSettingsBuilder, ServerSettingsBuilder, envfile}; +use fabro_interview::Interviewer; +use fabro_static::EnvVars; +use fabro_store::{ArtifactStore, Database}; use fabro_types::settings::ServerAuthMethod; -use fabro_types::{AuthMethod, IdpIdentity}; +use fabro_types::{AuthMethod, IdpIdentity, ServerSettings}; +use fabro_util::error::SharedError; +use fabro_workflow::handler::HandlerRegistry; +use object_store::memory::InMemory as MemoryObjectStore; +use ulid::Ulid; use crate::auth; use crate::ip_allowlist::IpAllowlistConfig; use crate::jwt_auth::{AuthMode, ConfiguredAuth}; #[cfg(test)] use crate::principal_middleware::{AuthContextSlot, RequestAuthContext}; -use crate::server::{self, AppState, RouterOptions}; +use crate::server::{ + self, AppState, AppStateConfig, EnvLookup, ResolvedAppStateSettings, RouterOptions, + build_app_state, process_env_var, +}; +use crate::server_secrets::ServerSecrets; pub const TEST_DEV_TOKEN: &str = "fabro_dev_abababababababababababababababababababababababababababababababab"; pub const TEST_SESSION_SECRET: &str = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; -#[doc(hidden)] +pub fn default_test_server_settings() -> ServerSettings { + ServerSettingsBuilder::from_toml( + r#" +_version = 1 + +[server.auth] +methods = ["dev-token"] +"#, + ) + .expect("default test server settings should resolve") +} + +pub fn test_app_state() -> Arc { + test_app_state_with_options(default_test_server_settings(), RunLayer::default(), 5) +} + +pub fn test_app_state_with_registry_factory( + registry_factory_override: impl Fn(Arc) -> HandlerRegistry + Send + Sync + 'static, +) -> Arc { + test_app_state_with_settings_and_registry_factory( + default_test_server_settings(), + RunLayer::default(), + registry_factory_override, + ) +} + +pub fn test_app_state_with_settings_and_registry_factory( + server_settings: ServerSettings, + manifest_run_defaults: RunLayer, + registry_factory_override: impl Fn(Arc) -> HandlerRegistry + Send + Sync + 'static, +) -> Arc { + test_app_state_with_options_and_registry_factory( + server_settings, + manifest_run_defaults, + 5, + registry_factory_override, + ) +} + +pub fn test_app_state_with_options_and_registry_factory( + server_settings: ServerSettings, + manifest_run_defaults: RunLayer, + max_concurrent_runs: usize, + registry_factory_override: impl Fn(Arc) -> HandlerRegistry + Send + Sync + 'static, +) -> Arc { + test_app_state_with_runtime_settings_and_options_and_registry_factory( + server_settings, + manifest_run_defaults, + max_concurrent_runs, + registry_factory_override, + ) +} + +pub fn test_app_state_with_options( + server_settings: ServerSettings, + manifest_run_defaults: RunLayer, + max_concurrent_runs: usize, +) -> Arc { + test_app_state_with_runtime_settings_and_options( + server_settings, + manifest_run_defaults, + max_concurrent_runs, + ) +} + +pub(crate) fn resolved_runtime_settings_for_tests( + server_settings: ServerSettings, + manifest_run_defaults: RunLayer, +) -> ResolvedAppStateSettings { + ResolvedAppStateSettings { + manifest_run_settings: RunSettingsBuilder::from_run_layer(&manifest_run_defaults) + .map_err(|err| SharedError::new(anyhow::Error::new(err))), + manifest_run_defaults, + server_settings, + } +} + +pub fn test_app_state_with_runtime_settings_and_registry_factory( + server_settings: ServerSettings, + manifest_run_defaults: RunLayer, + registry_factory_override: impl Fn(Arc) -> HandlerRegistry + Send + Sync + 'static, +) -> Arc { + test_app_state_with_runtime_settings_and_options_and_registry_factory( + server_settings, + manifest_run_defaults, + 5, + registry_factory_override, + ) +} + +pub fn test_app_state_with_runtime_settings_and_options_and_registry_factory( + server_settings: ServerSettings, + manifest_run_defaults: RunLayer, + max_concurrent_runs: usize, + registry_factory_override: impl Fn(Arc) -> HandlerRegistry + Send + Sync + 'static, +) -> Arc { + let (store, artifact_store) = test_store_bundle(); + let vault_path = test_secret_store_path(); + let server_env_path = vault_path.with_file_name("server.env"); + let env_lookup = default_env_lookup(); + let mut config = AppStateConfig { + resolved_settings: resolved_runtime_settings_for_tests( + server_settings, + manifest_run_defaults, + ), + registry_factory_override: None, + max_concurrent_runs, + store, + artifact_store, + vault_path, + server_secrets: load_test_server_secrets(server_env_path, HashMap::new()), + env_lookup, + github_api_base_url: None, + http_client: Some(fabro_http::test_http_client().expect("test HTTP client should build")), + }; + config.registry_factory_override = Some(Box::new(registry_factory_override)); + build_app_state(config).expect("test app state should build") +} + +pub fn test_app_state_with_runtime_settings_and_options( + server_settings: ServerSettings, + manifest_run_defaults: RunLayer, + max_concurrent_runs: usize, +) -> Arc { + test_app_state_with_runtime_settings_and_env_lookup_and_server_secret_env( + server_settings, + manifest_run_defaults, + max_concurrent_runs, + process_env_var, + &HashMap::new(), + ) +} + +pub fn test_app_state_with_runtime_settings_and_env_lookup( + server_settings: ServerSettings, + manifest_run_defaults: RunLayer, + max_concurrent_runs: usize, + env_lookup: impl Fn(&str) -> Option + Send + Sync + 'static, +) -> Arc { + test_app_state_with_runtime_settings_and_env_lookup_and_server_secret_env( + server_settings, + manifest_run_defaults, + max_concurrent_runs, + env_lookup, + &HashMap::new(), + ) +} + +pub fn test_app_state_with_runtime_settings_and_env_lookup_and_server_secret_env( + server_settings: ServerSettings, + manifest_run_defaults: RunLayer, + max_concurrent_runs: usize, + env_lookup: impl Fn(&str) -> Option + Send + Sync + 'static, + server_secret_env: &HashMap, +) -> Arc { + let (store, artifact_store) = test_store_bundle(); + let env_lookup: EnvLookup = Arc::new(env_lookup); + let vault_path = test_secret_store_path(); + let server_env_path = vault_path.with_file_name("server.env"); + build_app_state(AppStateConfig { + resolved_settings: resolved_runtime_settings_for_tests( + server_settings, + manifest_run_defaults, + ), + registry_factory_override: None, + max_concurrent_runs, + store, + artifact_store, + vault_path, + server_secrets: load_test_server_secrets(server_env_path, server_secret_env.clone()), + env_lookup, + github_api_base_url: None, + http_client: Some(fabro_http::test_http_client().expect("test HTTP client should build")), + }) + .expect("test app state should build") +} + +pub fn test_app_state_with_env_lookup( + server_settings: ServerSettings, + manifest_run_defaults: RunLayer, + max_concurrent_runs: usize, + env_lookup: impl Fn(&str) -> Option + Send + Sync + 'static, +) -> Arc { + test_app_state_with_runtime_settings_and_env_lookup( + server_settings, + manifest_run_defaults, + max_concurrent_runs, + env_lookup, + ) +} + +pub fn test_app_state_with_env_lookup_and_server_secret_env( + server_settings: ServerSettings, + manifest_run_defaults: RunLayer, + max_concurrent_runs: usize, + env_lookup: impl Fn(&str) -> Option + Send + Sync + 'static, + server_secret_env: &HashMap, +) -> Arc { + test_app_state_with_runtime_settings_and_env_lookup_and_server_secret_env( + server_settings, + manifest_run_defaults, + max_concurrent_runs, + env_lookup, + server_secret_env, + ) +} + +#[expect( + clippy::disallowed_methods, + reason = "test helper writes a fixture server.env with sync std::fs::write" +)] +pub fn test_app_state_with_runtime_settings_and_session_key( + server_settings: ServerSettings, + manifest_run_defaults: RunLayer, + session_secret: Option<&str>, +) -> Arc { + let vault_path = test_secret_store_path(); + let server_env_path = vault_path + .parent() + .expect("test secrets path should have parent") + .join("server.env"); + if let Some(session_secret) = session_secret { + std::fs::write( + &server_env_path, + format!("SESSION_SECRET={session_secret}\n"), + ) + .expect("test server env should be writable"); + } + let (store, artifact_store) = test_store_bundle(); + let env_lookup = default_env_lookup(); + build_app_state(AppStateConfig { + resolved_settings: resolved_runtime_settings_for_tests( + server_settings, + manifest_run_defaults, + ), + registry_factory_override: None, + max_concurrent_runs: 5, + store, + artifact_store, + vault_path, + server_secrets: load_test_server_secrets(server_env_path, HashMap::new()), + env_lookup, + github_api_base_url: None, + http_client: Some(fabro_http::test_http_client().expect("test HTTP client should build")), + }) + .expect("test app state should build") +} + +pub fn test_app_state_with_session_key( + server_settings: ServerSettings, + manifest_run_defaults: RunLayer, + session_secret: Option<&str>, +) -> Arc { + test_app_state_with_runtime_settings_and_session_key( + server_settings, + manifest_run_defaults, + session_secret, + ) +} + +pub fn test_app_state_with_store( + server_settings: ServerSettings, + manifest_run_defaults: RunLayer, + max_concurrent_runs: usize, + store: Arc, + artifact_store: ArtifactStore, +) -> Arc { + test_app_state_with_store_and_runtime_settings( + server_settings, + manifest_run_defaults, + max_concurrent_runs, + store, + artifact_store, + ) +} + +pub fn test_store_bundle() -> (Arc, ArtifactStore) { + let object_store: Arc = Arc::new(MemoryObjectStore::new()); + let store = Arc::new(fabro_store::Database::new( + Arc::clone(&object_store), + "", + Duration::from_millis(1), + None, + )); + let artifact_store = ArtifactStore::new(object_store, "artifacts"); + (store, artifact_store) +} + +pub fn test_app_state_with_store_and_runtime_settings( + server_settings: ServerSettings, + manifest_run_defaults: RunLayer, + max_concurrent_runs: usize, + store: Arc, + artifact_store: ArtifactStore, +) -> Arc { + let vault_path = test_secret_store_path(); + let server_env_path = vault_path.with_file_name("server.env"); + build_app_state(AppStateConfig { + resolved_settings: resolved_runtime_settings_for_tests( + server_settings, + manifest_run_defaults, + ), + registry_factory_override: None, + max_concurrent_runs, + store, + artifact_store, + vault_path, + server_secrets: load_test_server_secrets(server_env_path, HashMap::new()), + env_lookup: default_env_lookup(), + github_api_base_url: None, + http_client: Some(fabro_http::test_http_client().expect("test HTTP client should build")), + }) + .expect("test app state should build") +} + +pub(crate) fn default_env_lookup() -> EnvLookup { + Arc::new(process_env_var) +} + +pub(crate) fn load_test_server_secrets( + path: PathBuf, + env: HashMap, +) -> ServerSecrets { + let mut env = env; + let file_has_session_secret = envfile::read_env_file(&path) + .ok() + .is_some_and(|entries| entries.contains_key(EnvVars::SESSION_SECRET)); + if !env.contains_key(EnvVars::SESSION_SECRET) && !file_has_session_secret { + env.insert( + EnvVars::SESSION_SECRET.to_string(), + "server-test-session-key-0123456789".to_string(), + ); + } + ServerSecrets::load(path, env).expect("test server secrets should load") +} + +pub fn test_secret_store_path() -> PathBuf { + let dir = std::env::temp_dir().join(format!("fabro-test-{}", Ulid::new())); + std::fs::create_dir_all(&dir).expect("test temp dir should be creatable"); + dir.join("secrets.json") +} + #[must_use] pub fn test_auth_mode() -> AuthMode { AuthMode::Enabled(ConfiguredAuth { @@ -39,12 +395,10 @@ pub fn test_auth_mode() -> AuthMode { }) } -#[doc(hidden)] pub fn build_test_router(state: Arc) -> Router { with_test_user(server::build_router(state, test_auth_mode())) } -#[doc(hidden)] pub fn build_test_router_with_options( state: Arc, ip_allowlist_config: Arc, @@ -58,7 +412,6 @@ pub fn build_test_router_with_options( )) } -#[doc(hidden)] pub fn with_test_user(router: Router) -> Router { router.layer(middleware::from_fn(inject_test_user_bearer)) } @@ -92,7 +445,7 @@ fn issue_test_user_token() -> String { user_url: String::new(), auth_method: AuthMethod::DevToken, }, - Duration::days(3650), + ChronoDuration::days(3650), ) } diff --git a/lib/crates/fabro-server/src/web_auth.rs b/lib/crates/fabro-server/src/web_auth.rs index efeaf2bd7..4bb788bf3 100644 --- a/lib/crates/fabro-server/src/web_auth.rs +++ b/lib/crates/fabro-server/src/web_auth.rs @@ -972,7 +972,7 @@ client_id = "github-client-id" settings: fabro_types::ServerSettings, auth_mode: AuthMode, ) -> axum::Router { - let state = server::create_test_app_state_with_runtime_settings_and_session_key( + let state = crate::test_support::test_app_state_with_runtime_settings_and_session_key( settings, RunLayer::default(), Some("web-auth-test-key-material-0123456789"), @@ -1002,7 +1002,7 @@ client_id = "github-client-id" auth_mode: AuthMode, ) -> (axum::Router, Arc>>) { let captured = Arc::new(Mutex::new(Vec::new())); - let state = server::create_test_app_state_with_runtime_settings_and_session_key( + let state = crate::test_support::test_app_state_with_runtime_settings_and_session_key( settings, RunLayer::default(), Some("web-auth-test-key-material-0123456789"), @@ -1242,7 +1242,7 @@ client_id = "github-client-id" #[tokio::test] async fn auth_config_returns_real_methods_when_demo_cookie_set() { - let state = server::create_test_app_state_with_runtime_settings_and_session_key( + let state = crate::test_support::test_app_state_with_runtime_settings_and_session_key( github_settings("https://fabro.example"), RunLayer::default(), Some("web-auth-test-key-material-0123456789"), @@ -1335,7 +1335,7 @@ client_id = "github-client-id" #[tokio::test] async fn login_github_uses_injected_github_endpoints() { - let state = server::create_test_app_state_with_runtime_settings_and_session_key( + let state = crate::test_support::test_app_state_with_runtime_settings_and_session_key( github_settings("https://fabro.example"), RunLayer::default(), Some("web-auth-test-key-material-0123456789"), diff --git a/lib/crates/fabro-server/tests/it/api/cli_auth_token.rs b/lib/crates/fabro-server/tests/it/api/cli_auth_token.rs index 43fc13019..9304de9e7 100644 --- a/lib/crates/fabro-server/tests/it/api/cli_auth_token.rs +++ b/lib/crates/fabro-server/tests/it/api/cli_auth_token.rs @@ -6,9 +6,8 @@ use axum::http::{Request, StatusCode, header}; use base64::Engine; use fabro_server::ip_allowlist::IpAllowlistConfig; use fabro_server::jwt_auth::resolve_auth_mode_with_lookup; -use fabro_server::server::{ - RouterOptions, build_router_with_options, create_app_state_with_store_and_runtime_settings, -}; +use fabro_server::server::{RouterOptions, build_router_with_options}; +use fabro_server::test_support::test_app_state_with_store_and_runtime_settings; use fabro_store::{ArtifactStore, AuthCode, Database, RefreshToken}; use object_store::memory::InMemory; use sha2::{Digest, Sha256}; @@ -35,7 +34,7 @@ fn test_app(source: &str) -> (axum::Router, Arc) { }) .expect("auth mode should resolve"); let app = build_router_with_options( - create_app_state_with_store_and_runtime_settings( + test_app_state_with_store_and_runtime_settings( settings.server_settings, settings.manifest_run_defaults, 5, diff --git a/lib/crates/fabro-server/tests/it/api/routing.rs b/lib/crates/fabro-server/tests/it/api/routing.rs index 60a1c5199..af7903d51 100644 --- a/lib/crates/fabro-server/tests/it/api/routing.rs +++ b/lib/crates/fabro-server/tests/it/api/routing.rs @@ -8,11 +8,11 @@ use axum::http::{Method, Request, StatusCode}; use fabro_config::ServerSettingsBuilder; use fabro_server::ip_allowlist::{IpAllowlist, IpAllowlistConfig}; use fabro_server::jwt_auth::{AuthMode, resolve_auth_mode_with_lookup}; -use fabro_server::server::{ - RouterOptions, build_router, create_app_state, - create_app_state_with_runtime_settings_and_options, +use fabro_server::server::{RouterOptions, build_router}; +use fabro_server::test_support::{ + TEST_DEV_TOKEN, TEST_SESSION_SECRET, test_app_state, + test_app_state_with_runtime_settings_and_options, }; -use fabro_server::test_support::{TEST_DEV_TOKEN, TEST_SESSION_SECRET}; use tower::ServiceExt; use crate::helpers::{ @@ -44,7 +44,7 @@ fn spa_fixture_root() -> PathBuf { #[tokio::test] async fn old_unversioned_routes_return_404() { - let app = fabro_server::test_support::build_test_router(create_app_state()); + let app = fabro_server::test_support::build_test_router(test_app_state()); let cases = [(Method::POST, "/completions")]; @@ -62,7 +62,7 @@ async fn old_unversioned_routes_return_404() { #[tokio::test] async fn root_and_health_stay_at_root() { let app = fabro_server::test_support::build_test_router_with_options( - create_app_state(), + test_app_state(), Arc::new(IpAllowlistConfig::default()), RouterOptions { static_asset_root: Some(spa_fixture_root()), @@ -95,7 +95,7 @@ async fn root_and_health_stay_at_root() { #[tokio::test] async fn install_routes_are_absent_in_normal_mode() { - let app = fabro_server::test_support::build_test_router(create_app_state()); + let app = fabro_server::test_support::build_test_router(test_app_state()); let response = app .oneshot( @@ -113,7 +113,7 @@ async fn install_routes_are_absent_in_normal_mode() { #[tokio::test] async fn moved_routes_not_at_root_of_api_prefix() { - let app = fabro_server::test_support::build_test_router(create_app_state()); + let app = fabro_server::test_support::build_test_router(test_app_state()); for path in ["/api/v1/health", "/api/v1/"] { let req = Request::builder() @@ -128,7 +128,7 @@ async fn moved_routes_not_at_root_of_api_prefix() { #[tokio::test] async fn source_maps_are_not_served() { - let app = fabro_server::test_support::build_test_router(create_app_state()); + let app = fabro_server::test_support::build_test_router(test_app_state()); let request = Request::builder() .method("GET") @@ -149,7 +149,7 @@ async fn source_maps_are_not_served() { async fn web_enabled_serves_web_only_routes() { let auth_mode = dev_token_enabled_auth_mode(); let app = fabro_server::server::build_router_with_options( - create_app_state(), + test_app_state(), &auth_mode, Arc::new(IpAllowlistConfig::default()), RouterOptions { @@ -260,7 +260,7 @@ async fn web_enabled_serves_web_only_routes() { #[tokio::test] async fn toggle_demo_rejects_unauthenticated_requests() { - let app = build_router(create_app_state(), dev_token_enabled_auth_mode()); + let app = build_router(test_app_state(), dev_token_enabled_auth_mode()); let response = app .oneshot( @@ -283,7 +283,7 @@ async fn toggle_demo_rejects_unauthenticated_requests() { #[tokio::test] async fn toggle_demo_allows_authenticated_requests() { - let app = build_router(create_app_state(), dev_token_enabled_auth_mode()); + let app = build_router(test_app_state(), dev_token_enabled_auth_mode()); let response = checked_response( app.oneshot( @@ -314,7 +314,7 @@ async fn toggle_demo_allows_authenticated_requests() { #[tokio::test] async fn security_headers_are_applied_to_all_responses() { let app = fabro_server::test_support::build_test_router_with_options( - create_app_state(), + test_app_state(), Arc::new(IpAllowlistConfig::default()), RouterOptions { static_asset_root: Some(spa_fixture_root()), @@ -448,7 +448,7 @@ enabled = false ", ); let app = fabro_server::test_support::build_test_router_with_options( - create_app_state_with_runtime_settings_and_options( + test_app_state_with_runtime_settings_and_options( settings.server_settings, settings.manifest_run_defaults, 5, @@ -512,7 +512,7 @@ enabled = false ", ); let app = fabro_server::test_support::build_test_router_with_options( - create_app_state_with_runtime_settings_and_options( + test_app_state_with_runtime_settings_and_options( settings.server_settings, settings.manifest_run_defaults, 5, @@ -539,7 +539,7 @@ enabled = false #[tokio::test] async fn allowlist_blocks_non_allowlisted_api_requests() { let app = fabro_server::test_support::build_test_router_with_options( - create_app_state(), + test_app_state(), Arc::new(IpAllowlistConfig { allowlist: IpAllowlist::new(vec!["10.0.0.0/8".parse().unwrap()]), trusted_proxy_count: 0, @@ -561,7 +561,7 @@ async fn allowlist_blocks_non_allowlisted_api_requests() { #[tokio::test] async fn allowlist_exempts_health_checks() { let app = fabro_server::test_support::build_test_router_with_options( - create_app_state(), + test_app_state(), Arc::new(IpAllowlistConfig { allowlist: IpAllowlist::new(vec!["10.0.0.0/8".parse().unwrap()]), trusted_proxy_count: 0, diff --git a/lib/crates/fabro-server/tests/it/api/run_files.rs b/lib/crates/fabro-server/tests/it/api/run_files.rs index 4fce42837..079544c3f 100644 --- a/lib/crates/fabro-server/tests/it/api/run_files.rs +++ b/lib/crates/fabro-server/tests/it/api/run_files.rs @@ -12,7 +12,7 @@ use std::time::Duration; use axum::body::Body; use axum::http::{Request, StatusCode}; -use fabro_server::server::create_app_state_with_store; +use fabro_server::test_support::test_app_state_with_store; use fabro_store::{ArtifactStore, Database}; use fabro_types::RunId; use fabro_workflow::event as workflow_event; @@ -224,7 +224,7 @@ async fn submitted_run_without_sandbox_returns_empty_envelope() { async fn degraded_run_returns_file_diff_shape_without_meta_patch() { let settings = test_settings(); let (store, artifact_store) = store_bundle(); - let state = create_app_state_with_store( + let state = test_app_state_with_store( settings.server_settings, settings.manifest_run_defaults, 5, diff --git a/lib/crates/fabro-server/tests/it/api/settings.rs b/lib/crates/fabro-server/tests/it/api/settings.rs index d68b81b2b..7dd613997 100644 --- a/lib/crates/fabro-server/tests/it/api/settings.rs +++ b/lib/crates/fabro-server/tests/it/api/settings.rs @@ -1,6 +1,6 @@ use axum::body::Body; use axum::http::{Request, StatusCode}; -use fabro_server::server::create_app_state_with_runtime_settings_and_options; +use fabro_server::test_support::test_app_state_with_runtime_settings_and_options; use tower::ServiceExt; use crate::helpers::{response_json, settings_from_toml}; @@ -32,7 +32,7 @@ client_id = "Iv1.abcdef" "#, ); let app = fabro_server::test_support::build_test_router( - create_app_state_with_runtime_settings_and_options( + test_app_state_with_runtime_settings_and_options( settings.server_settings, settings.manifest_run_defaults, 5, diff --git a/lib/crates/fabro-server/tests/it/api/tcp.rs b/lib/crates/fabro-server/tests/it/api/tcp.rs index 2fcab6d6a..9b2703aa3 100644 --- a/lib/crates/fabro-server/tests/it/api/tcp.rs +++ b/lib/crates/fabro-server/tests/it/api/tcp.rs @@ -14,8 +14,8 @@ use fabro_config::{RuntimeDirectory, ServerSettingsBuilder}; use fabro_server::ip_allowlist::{IpAllowlist, IpAllowlistConfig}; use fabro_server::jwt_auth::{AuthMode, resolve_auth_mode_with_lookup}; use fabro_server::serve::{ServeArgs, serve_command}; -use fabro_server::server::{RouterOptions, build_router_with_options, create_app_state}; -use fabro_server::test_support::{TEST_DEV_TOKEN, TEST_SESSION_SECRET}; +use fabro_server::server::{RouterOptions, build_router_with_options}; +use fabro_server::test_support::{TEST_DEV_TOKEN, TEST_SESSION_SECRET, test_app_state}; use fabro_util::terminal::Styles; use tempfile::TempDir; use tokio::net::TcpListener; @@ -32,7 +32,7 @@ async fn start_tcp_server(auth_mode: AuthMode, ip_allowlist: Arc TestAppSettings { } pub(crate) fn test_app_state() -> Arc { - create_app_state() + server_test_app_state() } pub(crate) fn test_app_state_with_options( settings: TestAppSettings, max_concurrent_runs: usize, ) -> Arc { - create_app_state_with_runtime_settings_and_options_and_registry_factory( + test_app_state_with_runtime_settings_and_options_and_registry_factory( settings.server_settings, settings.manifest_run_defaults, max_concurrent_runs, @@ -114,7 +115,7 @@ pub(crate) fn test_app_with_scheduler(state: Arc) -> axum::Router { pub(crate) fn test_app_with_no_providers() -> axum::Router { let settings = test_settings(); - let state = create_app_state_with_runtime_settings_and_env_lookup( + let state = test_app_state_with_runtime_settings_and_env_lookup( settings.server_settings, settings.manifest_run_defaults, 5, @@ -126,7 +127,7 @@ pub(crate) fn test_app_with_no_providers() -> axum::Router { pub(crate) fn test_app_with_mock_anthropic(mock_base_url: &str) -> axum::Router { let base_url = mock_base_url.to_string(); let settings = test_settings(); - let state = create_app_state_with_runtime_settings_and_env_lookup( + let state = test_app_state_with_runtime_settings_and_env_lookup( settings.server_settings, settings.manifest_run_defaults, 5, diff --git a/lib/crates/fabro-server/tests/it/openapi_conformance.rs b/lib/crates/fabro-server/tests/it/openapi_conformance.rs index 74781e914..a3cf16982 100644 --- a/lib/crates/fabro-server/tests/it/openapi_conformance.rs +++ b/lib/crates/fabro-server/tests/it/openapi_conformance.rs @@ -11,7 +11,7 @@ use axum::body::Body; use axum::http::{Method, Request, StatusCode}; use fabro_server::install::{InstallAppState, build_install_router}; -use fabro_server::server::create_app_state_with_runtime_settings_and_env_lookup_and_server_secret_env; +use fabro_server::test_support::test_app_state_with_runtime_settings_and_env_lookup_and_server_secret_env; use serde_yaml::Value; use tower::ServiceExt; @@ -146,7 +146,7 @@ async fn github_webhook_spec_route_is_routable_when_webhook_secret_is_present() let secret = "test-webhook-secret".to_string(); let settings = test_settings(); let app = fabro_server::test_support::build_test_router( - create_app_state_with_runtime_settings_and_env_lookup_and_server_secret_env( + test_app_state_with_runtime_settings_and_env_lookup_and_server_secret_env( settings.server_settings, settings.manifest_run_defaults, 5, diff --git a/lib/crates/fabro-server/tests/it/scenario/lifecycle.rs b/lib/crates/fabro-server/tests/it/scenario/lifecycle.rs index 6031c122e..c0dfc3e53 100644 --- a/lib/crates/fabro-server/tests/it/scenario/lifecycle.rs +++ b/lib/crates/fabro-server/tests/it/scenario/lifecycle.rs @@ -3,9 +3,8 @@ use std::sync::Arc; use axum::body::Body; use axum::http::{Request, StatusCode}; use fabro_interview::Interviewer; -use fabro_server::server::{ - create_app_state_with_runtime_settings_and_registry_factory, spawn_scheduler, -}; +use fabro_server::server::spawn_scheduler; +use fabro_server::test_support::test_app_state_with_runtime_settings_and_registry_factory; use fabro_workflow::handler::HandlerRegistry; use fabro_workflow::handler::agent::AgentHandler; use fabro_workflow::handler::exit::ExitHandler; @@ -119,7 +118,7 @@ const GATE_DOT: &str = r#"digraph GateTest { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn full_http_lifecycle_approve_and_complete() { let settings = test_settings(); - let state = create_app_state_with_runtime_settings_and_registry_factory( + let state = test_app_state_with_runtime_settings_and_registry_factory( settings.server_settings, settings.manifest_run_defaults, gate_registry, @@ -207,7 +206,7 @@ async fn full_http_lifecycle_approve_and_complete() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn full_http_lifecycle_cancel() { let settings = test_settings(); - let state = create_app_state_with_runtime_settings_and_registry_factory( + let state = test_app_state_with_runtime_settings_and_registry_factory( settings.server_settings, settings.manifest_run_defaults, gate_registry, @@ -278,7 +277,7 @@ async fn full_http_lifecycle_cancel() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn cancel_at_human_gate_persists_cancelled_terminal_event() { let settings = test_settings(); - let state = create_app_state_with_runtime_settings_and_registry_factory( + let state = test_app_state_with_runtime_settings_and_registry_factory( settings.server_settings, settings.manifest_run_defaults, gate_registry,