karbon_framework/testing/
mod.rs1use std::net::SocketAddr;
2use tokio::net::TcpListener;
3
4use crate::config::Config;
5use crate::db::{Database, DbPool};
6use crate::http::AppState;
7use crate::mail::Mailer;
8
9
10pub struct TestApp {
25 pub addr: SocketAddr,
26 pub pool: DbPool,
27 pub client: reqwest::Client,
28}
29
30impl TestApp {
31 pub async fn spawn(router: axum::Router<AppState>) -> Self {
33 dotenvy::dotenv().ok();
34 let config = Config::from_env();
35 let db = Database::connect(&config)
36 .await
37 .expect("Failed to connect to test database");
38
39 let pool = db.pool().clone();
40
41 let mailer = if !config.smtp_host.is_empty() && !config.smtp_user.is_empty() {
42 Mailer::new(&config).ok()
43 } else {
44 None
45 };
46
47 let state = AppState {
48 db,
49 config,
50 mailer,
51 role_hierarchy: crate::security::default_hierarchy(),
52 };
53
54 let router = router.with_state(state);
55
56 let listener = TcpListener::bind("127.0.0.1:0")
57 .await
58 .expect("Failed to bind test listener");
59 let addr = listener.local_addr().unwrap();
60
61 tokio::spawn(async move {
62 if let Err(e) = axum::serve(
63 listener,
64 router.into_make_service_with_connect_info::<SocketAddr>(),
65 )
66 .await
67 {
68 tracing::error!("Test server error: {e}");
69 }
70 });
71
72 let client = reqwest::Client::builder()
73 .redirect(reqwest::redirect::Policy::none())
74 .build()
75 .expect("Failed to build HTTP client");
76
77 Self { addr, pool, client }
78 }
79
80 pub fn url(&self, path: &str) -> String {
82 format!("http://{}{}", self.addr, path)
83 }
84
85 pub async fn get(&self, path: &str) -> reqwest::Response {
87 self.client
88 .get(&self.url(path))
89 .send()
90 .await
91 .expect("Failed to send GET request")
92 }
93
94 pub async fn post_json<T: serde::Serialize>(&self, path: &str, body: &T) -> reqwest::Response {
96 self.client
97 .post(&self.url(path))
98 .json(body)
99 .send()
100 .await
101 .expect("Failed to send POST request")
102 }
103
104 pub async fn put_json<T: serde::Serialize>(&self, path: &str, body: &T) -> reqwest::Response {
106 self.client
107 .put(&self.url(path))
108 .json(body)
109 .send()
110 .await
111 .expect("Failed to send PUT request")
112 }
113
114 pub async fn delete(&self, path: &str) -> reqwest::Response {
116 self.client
117 .delete(&self.url(path))
118 .send()
119 .await
120 .expect("Failed to send DELETE request")
121 }
122
123 pub async fn get_auth(&self, path: &str, token: &str) -> reqwest::Response {
125 self.client
126 .get(&self.url(path))
127 .bearer_auth(token)
128 .send()
129 .await
130 .expect("Failed to send authenticated GET request")
131 }
132}