108 lines
3.3 KiB
Rust
108 lines
3.3 KiB
Rust
//! Shared harness for the HTTP integration tests.
|
|
//!
|
|
//! Boots the real Axum app (via [`shoplist_backend::build_router`]) against a
|
|
//! Postgres database named by the `TEST_DATABASE_URL` env var, on an ephemeral
|
|
//! port, with a cookie-aware client so session auth works end to end.
|
|
//!
|
|
//! When `TEST_DATABASE_URL` is unset, [`spawn`] returns `None` and each test
|
|
//! skips — so `cargo test` stays green without a database, and the pre-push
|
|
//! hook runs the full suite only when one is available.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use shoplist_backend::config::{Config, SmtpConfig, SmtpSecurity};
|
|
use shoplist_backend::mail::Mailer;
|
|
use shoplist_backend::state::AppState;
|
|
use shoplist_backend::{build_router, db, fetch};
|
|
|
|
pub struct TestApp {
|
|
pub base: String,
|
|
pub client: reqwest::Client,
|
|
}
|
|
|
|
impl TestApp {
|
|
/// Absolute URL for an `/api`-relative path.
|
|
pub fn url(&self, path: &str) -> String {
|
|
format!("{}/api{}", self.base, path)
|
|
}
|
|
|
|
/// A fresh, never-before-used email (tests share one DB; avoid collisions).
|
|
pub fn unique_email(&self) -> String {
|
|
format!("u{}@example.test", uuid::Uuid::new_v4().simple())
|
|
}
|
|
}
|
|
|
|
/// Boot the app, or `None` if no test database is configured.
|
|
pub async fn spawn() -> Option<TestApp> {
|
|
let database_url = std::env::var("TEST_DATABASE_URL").ok()?;
|
|
|
|
let pool = db::connect(&database_url)
|
|
.await
|
|
.expect("connect + migrate test database");
|
|
|
|
let config = Config {
|
|
database_url,
|
|
host: "127.0.0.1".into(),
|
|
port: 0,
|
|
session_secret: "test-session-secret-at-least-32-chars!!".into(),
|
|
public_app_url: "http://localhost:5173".into(),
|
|
cors_origins: vec!["http://localhost:5173".into()],
|
|
smtp: SmtpConfig {
|
|
// Points nowhere; register() swallows send failures, other tests
|
|
// never trigger mail.
|
|
host: "localhost".into(),
|
|
port: 1,
|
|
username: String::new(),
|
|
password: String::new(),
|
|
from: "Test <test@localhost>".into(),
|
|
security: SmtpSecurity::None,
|
|
},
|
|
refetch_interval_secs: 0, // no background worker in tests
|
|
refetch_min_age_secs: 21_600,
|
|
default_currency: "EUR".into(),
|
|
cookie_secure: false, // plain HTTP in tests
|
|
};
|
|
|
|
let mailer = Mailer::from_config(&config.smtp).expect("build test mailer");
|
|
let state = AppState {
|
|
pool,
|
|
config: Arc::new(config),
|
|
mailer,
|
|
http: fetch::http_client(),
|
|
};
|
|
|
|
let app = build_router(state).await.expect("build router");
|
|
|
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
|
|
.await
|
|
.expect("bind ephemeral port");
|
|
let addr = listener.local_addr().unwrap();
|
|
tokio::spawn(async move {
|
|
axum::serve(listener, app).await.unwrap();
|
|
});
|
|
|
|
let client = reqwest::Client::builder()
|
|
.cookie_store(true)
|
|
.build()
|
|
.expect("build cookie client");
|
|
|
|
Some(TestApp {
|
|
base: format!("http://{addr}"),
|
|
client,
|
|
})
|
|
}
|
|
|
|
/// Skip-or-run boilerplate: `let app = test_app!();`.
|
|
#[macro_export]
|
|
macro_rules! test_app {
|
|
() => {
|
|
match common::spawn().await {
|
|
Some(app) => app,
|
|
None => {
|
|
eprintln!("skipping: TEST_DATABASE_URL not set");
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
}
|