refactor(tests): introduce shared tests/common/ support module
Lift duplicated test-harness code (User factories, TestConfig builder, login/CSRF flow, form/json helpers, case-directory seeding, URL path builders) into `server/tests/common/` so future API changes touch one file instead of every integration test. Migrated batches: csrf_attack, auth, login, magic_link, security_headers (28 tests); analyze, case_page, delete_recording, close_watermark, retention_sweep (67 tests). All 95 tests still green. Net line delta across these 10 files: +1113 / -1896 (~780 lines removed), plus ~500 lines of new shared infrastructure under tests/common/.
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
//! `Config` construction for tests.
|
||||
//!
|
||||
//! Collapses the ~13 lookalike `test_config_*` / `build_config` helpers
|
||||
//! scattered across the integration tests into one fluent builder.
|
||||
//! Every builder call is a minimal override on top of
|
||||
//! `Config::test_default()`; the builder automatically derives the
|
||||
//! `api_key → slug` index map from the collected users.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use doctate_server::config::{Config, User};
|
||||
|
||||
/// Unique tempdir path for a given label. `process::id()` + UUID v4
|
||||
/// keeps parallel `cargo test` runs from colliding; the label lets
|
||||
/// humans identify which test owns which leftover directory when
|
||||
/// cleanup fails.
|
||||
pub fn unique_tmpdir(label: &str) -> PathBuf {
|
||||
std::env::temp_dir().join(format!(
|
||||
"doctate-test-{label}-{}-{}",
|
||||
std::process::id(),
|
||||
uuid::Uuid::new_v4()
|
||||
))
|
||||
}
|
||||
|
||||
/// Fluent builder for `Arc<Config>`. All methods take `self` by value
|
||||
/// and return `Self` so calls chain naturally:
|
||||
///
|
||||
/// ```ignore
|
||||
/// let cfg = TestConfig::new()
|
||||
/// .with_user(test_user("dr_a"))
|
||||
/// .with_llm(mock.uri())
|
||||
/// .build();
|
||||
/// ```
|
||||
pub struct TestConfig {
|
||||
data_path: Option<PathBuf>,
|
||||
users: Vec<User>,
|
||||
llm_url: Option<String>,
|
||||
llm_api_key: Option<String>,
|
||||
llm_model: Option<String>,
|
||||
whisper_url: Option<String>,
|
||||
whisper_timeout_seconds: Option<u64>,
|
||||
ollama_url: Option<String>,
|
||||
label: &'static str,
|
||||
}
|
||||
|
||||
impl TestConfig {
|
||||
/// Start a fresh builder. `label` is used only to name the tempdir
|
||||
/// when no explicit `data_path` is set via `with_data_path`.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
data_path: None,
|
||||
users: Vec::new(),
|
||||
llm_url: None,
|
||||
llm_api_key: None,
|
||||
llm_model: None,
|
||||
whisper_url: None,
|
||||
whisper_timeout_seconds: None,
|
||||
ollama_url: None,
|
||||
label: "common",
|
||||
}
|
||||
}
|
||||
|
||||
/// Label used when auto-generating the tempdir name.
|
||||
pub fn with_label(mut self, label: &'static str) -> Self {
|
||||
self.label = label;
|
||||
self
|
||||
}
|
||||
|
||||
/// Override the data path. By default `build()` generates a unique
|
||||
/// tempdir under `std::env::temp_dir()`.
|
||||
pub fn with_data_path(mut self, path: PathBuf) -> Self {
|
||||
self.data_path = Some(path);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a single user. Can be called repeatedly.
|
||||
pub fn with_user(mut self, user: User) -> Self {
|
||||
self.users.push(user);
|
||||
self
|
||||
}
|
||||
|
||||
/// Replace the user list outright.
|
||||
pub fn with_users(mut self, users: Vec<User>) -> Self {
|
||||
self.users = users;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure a hosted-provider LLM with the common test defaults
|
||||
/// (`api_key = "test-key"`, `model = "test-model"`). For Ollama-style
|
||||
/// endpoints that expect no `Authorization` header, use
|
||||
/// [`with_llm_explicit`] and pass an empty api_key.
|
||||
pub fn with_llm(mut self, url: impl Into<String>) -> Self {
|
||||
self.llm_url = Some(url.into());
|
||||
self.llm_api_key.get_or_insert_with(|| "test-key".into());
|
||||
self.llm_model.get_or_insert_with(|| "test-model".into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure the LLM with explicit values. `api_key` may be empty
|
||||
/// to exercise the Ollama-style no-auth path.
|
||||
pub fn with_llm_explicit(
|
||||
mut self,
|
||||
url: impl Into<String>,
|
||||
api_key: impl Into<String>,
|
||||
model: impl Into<String>,
|
||||
) -> Self {
|
||||
self.llm_url = Some(url.into());
|
||||
self.llm_api_key = Some(api_key.into());
|
||||
self.llm_model = Some(model.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Whisper ASR mock URL + timeout.
|
||||
pub fn with_whisper(mut self, url: impl Into<String>, timeout_seconds: u64) -> Self {
|
||||
self.whisper_url = Some(url.into());
|
||||
self.whisper_timeout_seconds = Some(timeout_seconds);
|
||||
self
|
||||
}
|
||||
|
||||
/// Ollama (oneliner generator) mock URL.
|
||||
pub fn with_ollama(mut self, url: impl Into<String>) -> Self {
|
||||
self.ollama_url = Some(url.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Materialize the config. Panics only if the workspace-defaults in
|
||||
/// `Config::test_default` themselves are broken (not reachable in
|
||||
/// practice).
|
||||
pub fn build(self) -> Arc<Config> {
|
||||
let data_path = self
|
||||
.data_path
|
||||
.unwrap_or_else(|| unique_tmpdir(self.label));
|
||||
let api_keys: HashMap<String, String> = self
|
||||
.users
|
||||
.iter()
|
||||
.map(|u| (u.api_key.clone(), u.slug.clone()))
|
||||
.collect();
|
||||
|
||||
let defaults = Config::test_default();
|
||||
Arc::new(Config {
|
||||
data_path,
|
||||
users: self.users,
|
||||
api_keys,
|
||||
llm_url: self.llm_url.unwrap_or(defaults.llm_url),
|
||||
llm_api_key: self.llm_api_key.unwrap_or(defaults.llm_api_key),
|
||||
llm_model: self.llm_model.unwrap_or(defaults.llm_model),
|
||||
whisper_url: self.whisper_url.unwrap_or(defaults.whisper_url),
|
||||
whisper_timeout_seconds: self
|
||||
.whisper_timeout_seconds
|
||||
.unwrap_or(defaults.whisper_timeout_seconds),
|
||||
ollama_url: self.ollama_url.unwrap_or(defaults.ollama_url),
|
||||
..Config::test_default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TestConfig {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user