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 #[cfg(feature = "templates")]
53 templates: crate::template::TemplateEngine::empty(),
54 };
55
56 let router = router.with_state(state);
57
58 let listener = TcpListener::bind("127.0.0.1:0")
59 .await
60 .expect("Failed to bind test listener");
61 let addr = listener.local_addr().unwrap();
62
63 tokio::spawn(async move {
64 if let Err(e) = axum::serve(
65 listener,
66 router.into_make_service_with_connect_info::<SocketAddr>(),
67 )
68 .await
69 {
70 tracing::error!("Test server error: {e}");
71 }
72 });
73
74 let client = reqwest::Client::builder()
75 .redirect(reqwest::redirect::Policy::none())
76 .build()
77 .expect("Failed to build HTTP client");
78
79 Self { addr, pool, client }
80 }
81
82 pub fn url(&self, path: &str) -> String {
84 format!("http://{}{}", self.addr, path)
85 }
86
87 pub async fn get(&self, path: &str) -> reqwest::Response {
89 self.client
90 .get(&self.url(path))
91 .send()
92 .await
93 .expect("Failed to send GET request")
94 }
95
96 pub async fn post_json<T: serde::Serialize>(&self, path: &str, body: &T) -> reqwest::Response {
98 self.client
99 .post(&self.url(path))
100 .json(body)
101 .send()
102 .await
103 .expect("Failed to send POST request")
104 }
105
106 pub async fn put_json<T: serde::Serialize>(&self, path: &str, body: &T) -> reqwest::Response {
108 self.client
109 .put(&self.url(path))
110 .json(body)
111 .send()
112 .await
113 .expect("Failed to send PUT request")
114 }
115
116 pub async fn delete(&self, path: &str) -> reqwest::Response {
118 self.client
119 .delete(&self.url(path))
120 .send()
121 .await
122 .expect("Failed to send DELETE request")
123 }
124
125 pub async fn get_auth(&self, path: &str, token: &str) -> reqwest::Response {
127 self.client
128 .get(&self.url(path))
129 .bearer_auth(token)
130 .send()
131 .await
132 .expect("Failed to send authenticated GET request")
133 }
134}