1pub mod core;
25pub mod plugins;
26pub mod adapters;
27pub mod handlers;
28pub mod error;
29pub mod types;
30
31pub use core::{BetterAuth, AuthBuilder, AuthConfig};
33pub use error::{AuthError, AuthResult};
34pub use types::{User, Session, Account, Verification, AuthRequest, AuthResponse, CreateVerification};
35
36#[cfg(feature = "axum")]
37pub use handlers::axum::AxumIntegration;
38
39#[cfg(test)]
40mod tests {
41 use super::*;
42 use crate::adapters::MemoryDatabaseAdapter;
43 use crate::plugins::EmailPasswordPlugin;
44 use crate::types::{HttpMethod, CreateUser};
45 use serde_json::json;
46 use std::sync::Arc;
47
48 fn test_config() -> AuthConfig {
49 AuthConfig::new("test-secret-key-that-is-at-least-32-characters-long")
50 .base_url("http://localhost:3000")
51 .password_min_length(8)
52 }
53
54 async fn create_test_auth() -> BetterAuth {
55 BetterAuth::new(test_config())
56 .database(MemoryDatabaseAdapter::new())
57 .plugin(EmailPasswordPlugin::new().enable_signup(true))
58 .build()
59 .await
60 .expect("Failed to create test auth instance")
61 }
62
63 #[tokio::test]
64 async fn test_auth_builder() {
65 let auth = create_test_auth().await;
66 assert_eq!(auth.plugin_names(), vec!["email-password"]);
67 assert_eq!(auth.config().secret, "test-secret-key-that-is-at-least-32-characters-long");
68 }
69
70 #[tokio::test]
71 async fn test_signup_flow() {
72 let auth = create_test_auth().await;
73
74 let signup_data = json!({
75 "email": "test@example.com",
76 "password": "password123",
77 "name": "Test User"
78 });
79
80 let mut request = AuthRequest::new(HttpMethod::Post, "/sign-up/email");
81 request.body = Some(signup_data.to_string().into_bytes());
82 request.headers.insert("content-type".to_string(), "application/json".to_string());
83
84 let response = auth.handle_request(request).await.expect("Signup request failed");
85
86 assert_eq!(response.status, 200);
87
88 let response_json: serde_json::Value = serde_json::from_slice(&response.body)
89 .expect("Failed to parse response JSON");
90
91 assert!(response_json["user"]["id"].is_string());
92 assert_eq!(response_json["user"]["email"], "test@example.com");
93 assert_eq!(response_json["user"]["name"], "Test User");
94 assert!(response_json["token"].is_string());
95 }
96
97 #[tokio::test]
98 async fn test_signin_flow() {
99 let auth = create_test_auth().await;
100
101 let signup_data = json!({
103 "email": "signin@example.com",
104 "password": "password123",
105 "name": "Signin User"
106 });
107
108 let mut signup_request = AuthRequest::new(HttpMethod::Post, "/sign-up/email");
109 signup_request.body = Some(signup_data.to_string().into_bytes());
110 signup_request.headers.insert("content-type".to_string(), "application/json".to_string());
111
112 let signup_response = auth.handle_request(signup_request).await.expect("Signup failed");
113 assert_eq!(signup_response.status, 200);
114
115 let signin_data = json!({
117 "email": "signin@example.com",
118 "password": "password123"
119 });
120
121 let mut signin_request = AuthRequest::new(HttpMethod::Post, "/sign-in/email");
122 signin_request.body = Some(signin_data.to_string().into_bytes());
123 signin_request.headers.insert("content-type".to_string(), "application/json".to_string());
124
125 let signin_response = auth.handle_request(signin_request).await.expect("Signin failed");
126 assert_eq!(signin_response.status, 200);
127
128 let response_json: serde_json::Value = serde_json::from_slice(&signin_response.body)
129 .expect("Failed to parse signin response");
130
131 assert_eq!(response_json["user"]["email"], "signin@example.com");
132 assert!(response_json["token"].is_string());
133 }
134
135 #[tokio::test]
136 async fn test_duplicate_email_signup() {
137 let auth = create_test_auth().await;
138
139 let signup_data = json!({
140 "name": "Duplicate User",
141 "email": "duplicate@example.com",
142 "password": "password123"
143 });
144
145 let mut request = AuthRequest::new(HttpMethod::Post, "/sign-up/email");
146 request.body = Some(signup_data.to_string().into_bytes());
147 request.headers.insert("content-type".to_string(), "application/json".to_string());
148
149 let response1 = auth.handle_request(request.clone()).await.expect("First signup failed");
151 assert_eq!(response1.status, 200);
152
153 let response2 = auth.handle_request(request).await.expect("Second signup request failed");
155 assert_eq!(response2.status, 409);
156 }
157
158 #[tokio::test]
159 async fn test_invalid_credentials_signin() {
160 let auth = create_test_auth().await;
161
162 let signin_data = json!({
164 "email": "nonexistent@example.com",
165 "password": "password123"
166 });
167
168 let mut request = AuthRequest::new(HttpMethod::Post, "/sign-in/email");
169 request.body = Some(signin_data.to_string().into_bytes());
170 request.headers.insert("content-type".to_string(), "application/json".to_string());
171
172 let response = auth.handle_request(request).await.expect("Request should not panic");
173 assert_eq!(response.status, 401);
174 }
175
176 #[tokio::test]
177 async fn test_weak_password_validation() {
178 let auth = create_test_auth().await;
179
180 let signup_data = json!({
181 "email": "weak@example.com",
182 "password": "123", "name": "Weak Password User"
184 });
185
186 let mut request = AuthRequest::new(HttpMethod::Post, "/sign-up/email");
187 request.body = Some(signup_data.to_string().into_bytes());
188 request.headers.insert("content-type".to_string(), "application/json".to_string());
189
190 let response = auth.handle_request(request).await.expect("Request should not panic");
191 assert_eq!(response.status, 400);
192
193 let response_json: serde_json::Value = serde_json::from_slice(&response.body)
194 .expect("Failed to parse response");
195 assert!(response_json["message"].as_str().unwrap_or("").contains("Password must be at least"));
196 }
197
198 #[tokio::test]
199 async fn test_session_management() {
200 let auth = create_test_auth().await;
201 let session_manager = auth.session_manager();
202
203 let database = auth.database();
205 let create_user = CreateUser::new()
206 .with_email("session@example.com")
207 .with_name("Session User");
208
209 let user = database.create_user(create_user).await.expect("Failed to create user");
210
211 let session = session_manager.create_session(&user, None, None).await
213 .expect("Failed to create session");
214
215 assert!(session.token.starts_with("session_"));
216 assert_eq!(session.user_id, user.id);
217 assert!(session.active);
218
219 let retrieved_session = session_manager.get_session(&session.token).await
221 .expect("Failed to get session")
222 .expect("Session not found");
223
224 assert_eq!(retrieved_session.id, session.id);
225 assert_eq!(retrieved_session.user_id, user.id);
226
227 session_manager.delete_session(&session.token).await
229 .expect("Failed to delete session");
230
231 let deleted_session = session_manager.get_session(&session.token).await
233 .expect("Failed to check deleted session");
234 assert!(deleted_session.is_none());
235 }
236
237 #[tokio::test]
238 async fn test_token_format_validation() {
239 let auth = create_test_auth().await;
240 let session_manager = auth.session_manager();
241
242 assert!(session_manager.validate_token_format("session_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN"));
244 assert!(!session_manager.validate_token_format("invalid_token"));
245 assert!(!session_manager.validate_token_format("session_short"));
246 assert!(!session_manager.validate_token_format(""));
247 }
248
249 #[tokio::test]
250 async fn test_health_check_route() {
251 let auth = create_test_auth().await;
252
253 let request = AuthRequest::new(HttpMethod::Get, "/health");
254 let response = auth.handle_request(request).await.expect("Health check failed");
255
256 assert_eq!(response.status, 404);
259 }
260
261 #[tokio::test]
262 async fn test_config_validation() {
263 let config = AuthConfig::new("");
265 assert!(config.validate().is_err());
266
267 let config = AuthConfig::new("short");
269 assert!(config.validate().is_err());
270
271 let config = AuthConfig::new("this-is-a-valid-32-character-secret-key");
273 assert!(config.validate().is_err());
274
275 let mut config = AuthConfig::new("this-is-a-valid-32-character-secret-key");
277 config.database = Some(Arc::new(MemoryDatabaseAdapter::new()));
278 assert!(config.validate().is_ok());
279 }
280}