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:
2026-04-22 10:42:51 +02:00
parent 2e3b5efe86
commit f438e972d4
17 changed files with 1808 additions and 1898 deletions
+80
View File
@@ -0,0 +1,80 @@
//! Factory functions for the `User` struct from `doctate_server::config`.
//!
//! Tests previously copy-pasted a 10-line `make_user` in every file.
//! These factories centralise the defaults (doctor role, bcrypt cost 4,
//! window 72 h, password "s") so an API change to `User` touches one
//! file, not twenty.
use doctate_server::config::{RetentionUserSettings, User};
/// Bcrypt cost factor used in all tests. The production default is 12;
/// tests drop it to 4 because the hash is computed on every `test_user()`
/// call and a cost-12 hash adds ~200 ms per test. Security is irrelevant
/// for test-only passwords; speed is not.
pub const TEST_BCRYPT_COST: u32 = 4;
/// Default plaintext password across tests. Individual tests that need
/// a specific password (e.g. for a login-wrong-password assertion) use
/// [`test_user_with_password`] instead.
pub const TEST_PASSWORD: &str = "s";
/// Bcrypt-hash the test password. Extracted so the cost factor lives in
/// one place.
fn hash_password(plain: &str) -> String {
bcrypt::hash(plain, TEST_BCRYPT_COST).unwrap()
}
/// Doctor user. `api_key = "key-{slug}"` — kept deterministic so tests
/// that send the X-API-Key header can compute it from the slug without
/// keeping a separate constant.
pub fn test_user(slug: &str) -> User {
User {
slug: slug.into(),
api_key: format!("key-{slug}"),
web_password: hash_password(TEST_PASSWORD),
role: "doctor".into(),
whisper: Default::default(),
retention: Default::default(),
window_hours: 72,
preview_lines: 2,
}
}
/// Admin variant — used by bulk/reset/purge-closed tests where the
/// handler enforces `role == "admin"`.
pub fn test_admin(slug: &str) -> User {
let mut u = test_user(slug);
u.role = "admin".into();
u
}
/// Doctor user with an explicit plaintext password. Used by tests that
/// log in with a specific password (e.g. wrong-password rejection).
pub fn test_user_with_password(slug: &str, password: &str) -> User {
let mut u = test_user(slug);
u.web_password = hash_password(password);
u
}
/// Doctor user with custom retention settings — for the retention sweep
/// tests that drive the lazy auto-close / auto-purge logic.
pub fn test_user_with_retention(
slug: &str,
auto_close_days: u32,
auto_delete_days: u32,
) -> User {
let mut u = test_user(slug);
u.retention = RetentionUserSettings {
auto_close_days,
auto_delete_days,
};
u
}
/// Doctor user with a custom `window_hours` — used by `/api/oneliners`
/// tests that verify the visibility window is server-authoritative.
pub fn test_user_with_window_hours(slug: &str, hours: u32) -> User {
let mut u = test_user(slug);
u.window_hours = hours;
u
}