summaryrefslogtreecommitdiff
path: root/src/test_support.rs
blob: 8f2d2bfc5d9baacf9637460795e6c217570b4972 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//! Test support utilities shared between unit and integration tests.
//!
//! Gated behind `cfg(any(test, feature = "integration"))`.
//! Provides thin wrappers around `pub(crate)` server internals so integration
//! tests can start a real server on a random port without exposing internal APIs,
//! plus common helpers (temp dirs, cleanup) used across unit test modules.

#![allow(clippy::unwrap_used, clippy::expect_used)]

use crate::server::{AppState, run_with_listener};
use anyhow::Result;
use std::path::{Path, PathBuf};
use tokio::net::TcpListener;

/// Start the HTTP server on the given listener, shutting down when `shutdown` resolves.
///
/// The server behaves identically to production — same middleware, same handlers.
///
/// # Errors
///
/// Returns an error if the server encounters a fatal I/O error.
pub async fn run_server(
    state: AppState,
    listener: TcpListener,
    shutdown: impl std::future::Future<Output = ()> + Send + 'static,
) -> Result<()> {
    run_with_listener(state, listener, shutdown).await
}

/// Install the SIGHUP configuration-reload handler for `state`.
///
/// Call this before sending SIGHUP in tests that exercise hot-reload.
/// It replaces the default signal disposition (terminate) with the production
/// reload handler, so the process stays alive after receiving the signal.
pub fn setup_sighup_handler(state: &AppState) {
    crate::server::setup_sighup_handler(state.clone());
}

/// Generate a unique ID for test isolation (timestamp + counter).
///
/// # Panics
///
/// Panics if the system clock is before the Unix epoch.
pub fn uuid() -> String {
    use std::sync::atomic::{AtomicU64, Ordering};
    use std::time::{SystemTime, UNIX_EPOCH};
    static COUNTER: AtomicU64 = AtomicU64::new(0);
    let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
    let count = COUNTER.fetch_add(1, Ordering::SeqCst);
    format!(
        "{}-{}-{}",
        duration.as_secs(),
        duration.subsec_nanos(),
        count
    )
}

/// Create a unique temporary directory for a test.
///
/// # Panics
///
/// Panics if the directory cannot be created.
pub async fn temp_dir(prefix: &str) -> PathBuf {
    let dir = std::env::temp_dir().join(format!("witryna-{}-{}", prefix, uuid()));
    tokio::fs::create_dir_all(&dir).await.unwrap();
    dir
}

/// Remove a temporary directory (ignores errors).
pub async fn cleanup(dir: &Path) {
    let _ = tokio::fs::remove_dir_all(dir).await;
}