mod compat;
use better_auth::BetterAuth;
use better_auth::adapters::{
AccountOps, MemoryDatabaseAdapter, SessionOps, UserOps, VerificationOps,
};
use compat::helpers::*;
use std::sync::Arc;
async fn create_test_auth_memory() -> Arc<BetterAuth<MemoryDatabaseAdapter>> {
TestHarness::minimal().await.into_arc()
}
async fn create_test_user_and_session(
auth: Arc<BetterAuth<MemoryDatabaseAdapter>>,
) -> (String, String) {
let req = post_json(
"/sign-up/email",
serde_json::json!({
"email": "integration@test.com",
"password": "password123",
"name": "Integration Test User"
}),
);
let (status, json) = send_request(&auth, req).await;
assert_eq!(status, 200);
let user_id = json["user"]["id"].as_str().unwrap().to_string();
let session_token = json["token"].as_str().unwrap().to_string();
(user_id, session_token)
}
#[tokio::test]
async fn test_get_session_integration() {
let auth = create_test_auth_memory().await;
let (_user_id, session_token) = create_test_user_and_session(auth.clone()).await;
let (status, response_data) =
send_request(&auth, get_with_auth("/get-session", &session_token)).await;
assert_eq!(status, 200);
assert!(response_data["session"]["token"].is_string());
assert!(response_data["user"]["id"].is_string());
assert_eq!(response_data["user"]["email"], "integration@test.com");
}
#[tokio::test]
async fn test_sign_out_integration() {
let auth = create_test_auth_memory().await;
let (_user_id, session_token) = create_test_user_and_session(auth.clone()).await;
let (status, response_data) =
send_request(&auth, post_with_auth("/sign-out", &session_token)).await;
assert_eq!(status, 200);
assert_eq!(response_data["success"], true);
let (status2, _) = send_request(&auth, get_with_auth("/get-session", &session_token)).await;
assert_eq!(status2, 401); }
#[tokio::test]
async fn test_list_sessions_integration() {
let auth = create_test_auth_memory().await;
let (_user_id, session_token) = create_test_user_and_session(auth.clone()).await;
let (status, response_data) =
send_request(&auth, get_with_auth("/list-sessions", &session_token)).await;
assert_eq!(status, 200);
let sessions = response_data.as_array().expect("expected array response");
assert_eq!(sessions.len(), 1);
assert!(sessions[0]["token"].is_string());
}
#[tokio::test]
async fn test_revoke_session_integration() {
let auth = create_test_auth_memory().await;
let (user_id, session_token1) = create_test_user_and_session(auth.clone()).await;
use better_auth::types::CreateSession;
use chrono::{Duration, Utc};
let create_session = CreateSession {
user_id: user_id.clone(),
expires_at: Utc::now() + Duration::hours(24),
ip_address: Some("192.168.1.1".to_string()),
user_agent: Some("test-agent-2".to_string()),
impersonated_by: None,
active_organization_id: None,
};
let session2 = auth
.database()
.create_session(create_session)
.await
.unwrap();
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert(
"authorization".to_string(),
format!("Bearer {}", session_token1),
);
let revoke_data = serde_json::json!({
"token": session2.token
});
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/revoke-session".to_string(),
headers,
Some(revoke_data.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let body_str = String::from_utf8(response.body).unwrap();
let response_data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
assert_eq!(response_data["status"], true);
}
#[tokio::test]
async fn test_revoke_sessions_integration() {
let auth = create_test_auth_memory().await;
let (_user_id, session_token) = create_test_user_and_session(auth.clone()).await;
let (status, response_data) =
send_request(&auth, post_with_auth("/revoke-sessions", &session_token)).await;
assert_eq!(status, 200);
assert_eq!(response_data["status"], true);
}
#[tokio::test]
async fn test_unauthorized_session_access() {
let auth = create_test_auth_memory().await;
let (status, _) = send_request(&auth, get_request("/get-session")).await;
assert_eq!(status, 401);
}
#[tokio::test]
async fn test_forget_password_integration() {
let auth = create_test_auth_memory().await;
let (_user_id, _session_token) = create_test_user_and_session(auth.clone()).await;
let (status, response_data) = send_request(
&auth,
post_json(
"/forget-password",
serde_json::json!({
"email": "integration@test.com",
"redirectTo": "http://localhost:3000/reset"
}),
),
)
.await;
assert_eq!(status, 200);
assert_eq!(response_data["status"], true);
}
#[tokio::test]
async fn test_reset_password_integration() {
let auth = create_test_auth_memory().await;
let (_user_id, _session_token) = create_test_user_and_session(auth.clone()).await;
use better_auth::types::CreateVerification;
use chrono::{Duration, Utc};
use uuid::Uuid;
let reset_token = format!("reset_{}", Uuid::new_v4());
let create_verification = CreateVerification {
identifier: "integration@test.com".to_string(),
value: reset_token.clone(),
expires_at: Utc::now() + Duration::hours(24),
};
auth.database()
.create_verification(create_verification)
.await
.unwrap();
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
let reset_data = serde_json::json!({
"newPassword": "NewPassword123!",
"token": reset_token
});
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/reset-password".to_string(),
headers,
Some(reset_data.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let body_str = String::from_utf8(response.body).unwrap();
let response_data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
assert_eq!(response_data["status"], true);
}
#[tokio::test]
async fn test_change_password_integration() {
let auth = create_test_auth_memory().await;
let (_user_id, session_token) = create_test_user_and_session(auth.clone()).await;
let (status, response_data) = send_request(
&auth,
post_json_with_auth(
"/change-password",
serde_json::json!({
"currentPassword": "password123",
"newPassword": "NewPassword123!",
"revokeOtherSessions": "false"
}),
&session_token,
),
)
.await;
assert_eq!(status, 200);
assert!(response_data["user"]["id"].is_string());
assert!(response_data["token"].is_null()); }
#[tokio::test]
async fn test_change_password_with_revocation_integration() {
let auth = create_test_auth_memory().await;
let (_user_id, session_token) = create_test_user_and_session(auth.clone()).await;
let (status, response_data) = send_request(
&auth,
post_json_with_auth(
"/change-password",
serde_json::json!({
"currentPassword": "password123",
"newPassword": "NewPassword123!",
"revokeOtherSessions": "true"
}),
&session_token,
),
)
.await;
assert_eq!(status, 200);
assert!(response_data["user"]["id"].is_string());
assert!(response_data["token"].is_string()); }
#[tokio::test]
async fn test_reset_password_token_integration() {
let auth = create_test_auth_memory().await;
let (_user_id, _session_token) = create_test_user_and_session(auth.clone()).await;
use better_auth::types::CreateVerification;
use chrono::{Duration, Utc};
use uuid::Uuid;
let reset_token = format!("reset_{}", Uuid::new_v4());
let create_verification = CreateVerification {
identifier: "integration@test.com".to_string(),
value: reset_token.clone(),
expires_at: Utc::now() + Duration::hours(24),
};
auth.database()
.create_verification(create_verification)
.await
.unwrap();
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Get,
format!("/reset-password/{}", reset_token),
HashMap::new(),
None,
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let body_str = String::from_utf8(response.body).unwrap();
let response_data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
assert_eq!(response_data["token"], reset_token);
}
#[tokio::test]
async fn test_ok_endpoint() {
let auth = create_test_auth_memory().await;
let (status, response_data) = send_request(&auth, get_request("/ok")).await;
assert_eq!(status, 200);
assert_eq!(response_data["ok"], true);
}
#[tokio::test]
async fn test_error_endpoint() {
let auth = create_test_auth_memory().await;
let (status, response_data) = send_request(&auth, get_request("/error")).await;
assert_eq!(status, 200);
assert_eq!(response_data["ok"], false);
}
#[tokio::test]
async fn test_get_session_post_integration() {
let auth = create_test_auth_memory().await;
let (_user_id, session_token) = create_test_user_and_session(auth.clone()).await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert(
"authorization".to_string(),
format!("Bearer {}", session_token),
);
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/get-session".to_string(),
headers,
Some(b"{}".to_vec()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let body_str = String::from_utf8(response.body).unwrap();
let response_data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
assert!(response_data["session"]["token"].is_string());
assert!(response_data["user"]["id"].is_string());
assert_eq!(response_data["user"]["email"], "integration@test.com");
}
#[tokio::test]
async fn test_delete_user_post_method() {
let auth = create_test_auth_memory().await;
let (_user_id, session_token) = create_test_user_and_session(auth.clone()).await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert(
"authorization".to_string(),
format!("Bearer {}", session_token),
);
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/delete-user".to_string(),
headers,
Some(b"{}".to_vec()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let body_str = String::from_utf8(response.body).unwrap();
let response_data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
assert_eq!(response_data["success"], true);
}
#[tokio::test]
async fn test_set_password_success() {
let auth = create_test_auth_memory().await;
use better_auth::types::{AuthRequest, CreateSession, CreateUser};
use chrono::{Duration, Utc};
use std::collections::HashMap;
let create_user = CreateUser::new()
.with_email("social@test.com")
.with_name("Social User");
let user = auth.database().create_user(create_user).await.unwrap();
let create_session = CreateSession {
user_id: user.id.clone(),
expires_at: Utc::now() + Duration::hours(24),
ip_address: None,
user_agent: None,
impersonated_by: None,
active_organization_id: None,
};
let session = auth
.database()
.create_session(create_session)
.await
.unwrap();
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
headers.insert(
"authorization".to_string(),
format!("Bearer {}", session.token),
);
let set_data = serde_json::json!({
"newPassword": "MyNewPassword123"
});
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/set-password".to_string(),
headers,
Some(set_data.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let body_str = String::from_utf8(response.body).unwrap();
let response_data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
assert_eq!(response_data["status"], true);
}
#[tokio::test]
async fn test_set_password_already_has_password() {
let auth = create_test_auth_memory().await;
let (_user_id, session_token) = create_test_user_and_session(auth.clone()).await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
headers.insert(
"authorization".to_string(),
format!("Bearer {}", session_token),
);
let set_data = serde_json::json!({
"newPassword": "AnotherPassword123"
});
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/set-password".to_string(),
headers,
Some(set_data.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 400);
}
#[tokio::test]
async fn test_set_password_unauthenticated() {
let auth = create_test_auth_memory().await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
let set_data = serde_json::json!({
"newPassword": "SomePassword123"
});
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/set-password".to_string(),
headers,
Some(set_data.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 401);
}
#[tokio::test]
async fn test_revoke_other_sessions_integration() {
let auth = create_test_auth_memory().await;
let (user_id, session_token1) = create_test_user_and_session(auth.clone()).await;
use better_auth::types::{AuthRequest, CreateSession};
use chrono::{Duration, Utc};
use std::collections::HashMap;
let create_session = CreateSession {
user_id: user_id.clone(),
expires_at: Utc::now() + Duration::hours(24),
ip_address: Some("192.168.1.1".to_string()),
user_agent: Some("other-agent".to_string()),
impersonated_by: None,
active_organization_id: None,
};
let session2 = auth
.database()
.create_session(create_session)
.await
.unwrap();
let mut headers = HashMap::new();
headers.insert(
"authorization".to_string(),
format!("Bearer {}", session_token1),
);
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/revoke-other-sessions".to_string(),
headers,
Some(b"{}".to_vec()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let s1 = auth.database().get_session(&session_token1).await.unwrap();
assert!(s1.is_some());
let s2 = auth.database().get_session(&session2.token).await.unwrap();
assert!(s2.is_none());
}
#[tokio::test]
async fn test_cookie_based_auth() {
let auth = create_test_auth_memory().await;
let (_user_id, session_token) = create_test_user_and_session(auth.clone()).await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert(
"cookie".to_string(),
format!("better-auth.session-token={}; other=value", session_token),
);
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Get,
"/get-session".to_string(),
headers,
None,
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let body_str = String::from_utf8(response.body).unwrap();
let response_data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
assert_eq!(response_data["user"]["email"], "integration@test.com");
}
#[tokio::test]
async fn test_bearer_takes_precedence_over_cookie() {
let auth = create_test_auth_memory().await;
let (_user_id, session_token) = create_test_user_and_session(auth.clone()).await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert(
"authorization".to_string(),
format!("Bearer {}", session_token),
);
headers.insert(
"cookie".to_string(),
"better-auth.session-token=invalid_token".to_string(),
);
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Get,
"/get-session".to_string(),
headers,
None,
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
}
#[tokio::test]
async fn test_unauthorized_password_operations() {
let auth = create_test_auth_memory().await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
let change_data = serde_json::json!({
"currentPassword": "password123",
"newPassword": "NewPassword123!"
});
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/change-password".to_string(),
headers,
Some(change_data.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 401);
}
#[tokio::test]
async fn test_change_email_success() {
let auth = create_test_auth_memory().await;
let (_user_id, session_token) = create_test_user_and_session(auth.clone()).await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
headers.insert(
"authorization".to_string(),
format!("Bearer {}", session_token),
);
let body = serde_json::json!({ "newEmail": "newemail@test.com" });
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/change-email".to_string(),
headers,
Some(body.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let body_str = String::from_utf8(response.body).unwrap();
let data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
assert_eq!(data["status"], true);
assert_eq!(data["message"], "Email updated");
}
#[tokio::test]
async fn test_change_email_duplicate() {
let auth = create_test_auth_memory().await;
use better_auth::types::{AuthRequest, CreateUser};
use std::collections::HashMap;
let (_user_id, session_token) = create_test_user_and_session(auth.clone()).await;
let create_user = CreateUser::new()
.with_email("existing@test.com")
.with_name("Existing User");
auth.database().create_user(create_user).await.unwrap();
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
headers.insert(
"authorization".to_string(),
format!("Bearer {}", session_token),
);
let body = serde_json::json!({ "newEmail": "existing@test.com" });
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/change-email".to_string(),
headers,
Some(body.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 409);
}
#[tokio::test]
async fn test_change_email_unauthenticated() {
let auth = create_test_auth_memory().await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
let body = serde_json::json!({ "newEmail": "x@y.com" });
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/change-email".to_string(),
headers,
Some(body.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 401);
}
#[tokio::test]
async fn test_delete_user_callback_success() {
let auth = create_test_auth_memory().await;
let (user_id, _session_token) = create_test_user_and_session(auth.clone()).await;
use better_auth::types::{AuthRequest, CreateVerification};
use chrono::{Duration, Utc};
use std::collections::HashMap;
let token = format!("delete_{}", uuid::Uuid::new_v4());
let create_verification = CreateVerification {
identifier: user_id.clone(),
value: token.clone(),
expires_at: Utc::now() + Duration::hours(24),
};
auth.database()
.create_verification(create_verification)
.await
.unwrap();
let mut query = HashMap::new();
query.insert("token".to_string(), token);
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Get,
"/delete-user/callback".to_string(),
HashMap::new(),
None,
query,
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let body_str = String::from_utf8(response.body).unwrap();
let data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
assert_eq!(data["success"], true);
let user = auth.database().get_user_by_id(&user_id).await.unwrap();
assert!(user.is_none());
}
#[tokio::test]
async fn test_delete_user_callback_invalid_token() {
let auth = create_test_auth_memory().await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut query = HashMap::new();
query.insert("token".to_string(), "invalid_token".to_string());
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Get,
"/delete-user/callback".to_string(),
HashMap::new(),
None,
query,
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 400);
}
#[tokio::test]
async fn test_list_accounts_empty() {
let auth = create_test_auth_memory().await;
let (_user_id, session_token) = create_test_user_and_session(auth.clone()).await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert(
"authorization".to_string(),
format!("Bearer {}", session_token),
);
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Get,
"/list-accounts".to_string(),
headers,
None,
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let body_str = String::from_utf8(response.body).unwrap();
let accounts: Vec<serde_json::Value> = serde_json::from_str(&body_str).unwrap();
assert_eq!(accounts.len(), 0); }
#[tokio::test]
async fn test_list_accounts_with_account() {
let auth = create_test_auth_memory().await;
let (user_id, session_token) = create_test_user_and_session(auth.clone()).await;
use better_auth::types::{AuthRequest, CreateAccount};
use std::collections::HashMap;
let create_account = CreateAccount {
account_id: "12345".to_string(),
provider_id: "google".to_string(),
user_id: user_id.clone(),
access_token: Some("access_token".to_string()),
refresh_token: None,
id_token: None,
access_token_expires_at: None,
refresh_token_expires_at: None,
scope: Some("email profile".to_string()),
password: None,
};
auth.database()
.create_account(create_account)
.await
.unwrap();
let mut headers = HashMap::new();
headers.insert(
"authorization".to_string(),
format!("Bearer {}", session_token),
);
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Get,
"/list-accounts".to_string(),
headers,
None,
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let body_str = String::from_utf8(response.body).unwrap();
let accounts: Vec<serde_json::Value> = serde_json::from_str(&body_str).unwrap();
assert_eq!(accounts.len(), 1);
assert_eq!(accounts[0]["provider"], "google");
assert_eq!(
accounts[0]["scopes"],
serde_json::json!(["email", "profile"])
);
assert!(accounts[0].get("access_token").is_none());
assert!(accounts[0].get("password").is_none());
}
#[tokio::test]
async fn test_unlink_account_success() {
let auth = create_test_auth_memory().await;
let (user_id, session_token) = create_test_user_and_session(auth.clone()).await;
use better_auth::types::{AuthRequest, CreateAccount};
use std::collections::HashMap;
for provider in &["google", "github"] {
let create_account = CreateAccount {
account_id: format!("id_{}", provider),
provider_id: provider.to_string(),
user_id: user_id.clone(),
access_token: None,
refresh_token: None,
id_token: None,
access_token_expires_at: None,
refresh_token_expires_at: None,
scope: None,
password: None,
};
auth.database()
.create_account(create_account)
.await
.unwrap();
}
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
headers.insert(
"authorization".to_string(),
format!("Bearer {}", session_token),
);
let unlink_data = serde_json::json!({
"providerId": "google"
});
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/unlink-account".to_string(),
headers,
Some(unlink_data.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let accounts = auth.database().get_user_accounts(&user_id).await.unwrap();
assert_eq!(accounts.len(), 1);
assert_eq!(accounts[0].provider_id, "github");
}
#[tokio::test]
async fn test_unlink_last_account_fails() {
let auth = create_test_auth_memory().await;
use better_auth::types::{AuthRequest, CreateAccount, CreateSession, CreateUser};
use chrono::{Duration, Utc};
use std::collections::HashMap;
let create_user = CreateUser::new()
.with_email("social-only@test.com")
.with_name("Social Only");
let user = auth.database().create_user(create_user).await.unwrap();
let create_session = CreateSession {
user_id: user.id.clone(),
expires_at: Utc::now() + Duration::hours(24),
ip_address: None,
user_agent: None,
impersonated_by: None,
active_organization_id: None,
};
let session = auth
.database()
.create_session(create_session)
.await
.unwrap();
let create_account = CreateAccount {
account_id: "id_google".to_string(),
provider_id: "google".to_string(),
user_id: user.id.clone(),
access_token: None,
refresh_token: None,
id_token: None,
access_token_expires_at: None,
refresh_token_expires_at: None,
scope: None,
password: None,
};
auth.database()
.create_account(create_account)
.await
.unwrap();
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
headers.insert(
"authorization".to_string(),
format!("Bearer {}", session.token),
);
let unlink_data = serde_json::json!({
"providerId": "google"
});
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/unlink-account".to_string(),
headers,
Some(unlink_data.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 400); }
#[tokio::test]
async fn test_list_accounts_unauthenticated() {
let auth = create_test_auth_memory().await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Get,
"/list-accounts".to_string(),
HashMap::new(),
None,
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 401);
}
#[tokio::test]
async fn test_sign_up_with_username_and_sign_in() {
let auth = create_test_auth_memory().await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let signup_data = serde_json::json!({
"email": "usernameguy@test.com",
"password": "password123",
"name": "Username Guy",
"username": "cool_user",
"displayUsername": "Cool User"
});
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/sign-up/email".to_string(),
headers.clone(),
Some(signup_data.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let body_str = String::from_utf8(response.body).unwrap();
let data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
assert_eq!(data["user"]["username"], "cool_user");
assert_eq!(data["user"]["displayUsername"], "Cool User");
let signin_data = serde_json::json!({
"username": "cool_user",
"password": "password123"
});
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/sign-in/username".to_string(),
headers,
Some(signin_data.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let body_str = String::from_utf8(response.body).unwrap();
let data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
assert!(data["token"].is_string());
assert_eq!(data["user"]["username"], "cool_user");
assert_eq!(data["user"]["email"], "usernameguy@test.com");
}
#[tokio::test]
async fn test_sign_in_username_wrong_password() {
let auth = create_test_auth_memory().await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let signup_data = serde_json::json!({
"email": "wrongpw@test.com",
"password": "password123",
"name": "Wrong PW",
"username": "wrongpw_user"
});
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/sign-up/email".to_string(),
headers.clone(),
Some(signup_data.to_string().into_bytes()),
HashMap::new(),
);
auth.handle_request(request).await.unwrap();
let signin_data = serde_json::json!({
"username": "wrongpw_user",
"password": "wrong_password"
});
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/sign-in/username".to_string(),
headers,
Some(signin_data.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 401);
}
#[tokio::test]
async fn test_sign_in_username_nonexistent() {
let auth = create_test_auth_memory().await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let signin_data = serde_json::json!({
"username": "no_such_user",
"password": "password123"
});
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/sign-in/username".to_string(),
headers,
Some(signin_data.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 401);
}
async fn create_auth_with_apikey() -> (Arc<BetterAuth<MemoryDatabaseAdapter>>, String, String) {
let auth = create_test_auth_memory().await;
let (user_id, session_token) = create_test_user_and_session(auth.clone()).await;
(auth, user_id, session_token)
}
async fn create_api_key(
auth: &BetterAuth<MemoryDatabaseAdapter>,
token: &str,
body: serde_json::Value,
) -> (String, String) {
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
headers.insert("authorization".to_string(), format!("Bearer {}", token));
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/api-key/create".to_string(),
headers,
Some(body.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let body_str = String::from_utf8(response.body).unwrap();
let data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
let key = data["key"].as_str().unwrap().to_string();
let id = data["id"].as_str().unwrap().to_string();
(key, id)
}
#[tokio::test]
async fn test_api_key_create() {
let (auth, _user_id, token) = create_auth_with_apikey().await;
let (key, id) = create_api_key(
&auth,
&token,
serde_json::json!({
"name": "test-key",
"prefix": "sk_"
}),
)
.await;
assert!(!key.is_empty(), "key should not be empty");
assert!(key.starts_with("sk_"), "key should start with prefix");
assert!(!id.is_empty(), "id should not be empty");
}
#[tokio::test]
async fn test_api_key_create_with_options() {
let (auth, _user_id, token) = create_auth_with_apikey().await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
headers.insert("authorization".to_string(), format!("Bearer {}", token));
let body = serde_json::json!({
"name": "limited-key",
"remaining": 100,
"expiresIn": 3600000,
"rateLimitEnabled": true,
"rateLimitMax": 10,
"rateLimitTimeWindow": 60000
});
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/api-key/create".to_string(),
headers,
Some(body.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let body_str = String::from_utf8(response.body).unwrap();
let data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
assert!(data["key"].is_string());
assert_eq!(data["name"], "limited-key");
assert_eq!(data["remaining"], 100);
assert_eq!(data["rateLimitEnabled"], true);
assert_eq!(data["rateLimitMax"], 10);
assert!(data["expiresAt"].is_string());
}
#[tokio::test]
async fn test_api_key_get() {
let (auth, _user_id, token) = create_auth_with_apikey().await;
let (_key, id) = create_api_key(&auth, &token, serde_json::json!({"name": "get-test"})).await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert("authorization".to_string(), format!("Bearer {}", token));
let mut query = HashMap::new();
query.insert("id".to_string(), id.clone());
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Get,
"/api-key/get".to_string(),
headers,
None,
query,
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let body_str = String::from_utf8(response.body).unwrap();
let data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
assert_eq!(data["id"], id);
assert_eq!(data["name"], "get-test");
assert!(data["enabled"].as_bool().unwrap());
assert!(data.get("keyHash").is_none());
assert!(data.get("key_hash").is_none());
}
#[tokio::test]
async fn test_api_key_list() {
let (auth, _user_id, token) = create_auth_with_apikey().await;
create_api_key(&auth, &token, serde_json::json!({"name": "key-1"})).await;
create_api_key(&auth, &token, serde_json::json!({"name": "key-2"})).await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert("authorization".to_string(), format!("Bearer {}", token));
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Get,
"/api-key/list".to_string(),
headers,
None,
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let body_str = String::from_utf8(response.body).unwrap();
let data: Vec<serde_json::Value> = serde_json::from_str(&body_str).unwrap();
assert_eq!(data.len(), 2);
}
#[tokio::test]
async fn test_api_key_update() {
let (auth, _user_id, token) = create_auth_with_apikey().await;
let (_key, id) =
create_api_key(&auth, &token, serde_json::json!({"name": "original-name"})).await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
headers.insert("authorization".to_string(), format!("Bearer {}", token));
let update_body = serde_json::json!({
"id": id,
"name": "updated-name",
"enabled": false,
"remaining": 50
});
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/api-key/update".to_string(),
headers,
Some(update_body.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let body_str = String::from_utf8(response.body).unwrap();
let data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
assert_eq!(data["id"], id);
assert_eq!(data["name"], "updated-name");
assert_eq!(data["enabled"], false);
assert_eq!(data["remaining"], 50);
}
#[tokio::test]
async fn test_api_key_delete() {
let (auth, _user_id, token) = create_auth_with_apikey().await;
let (_key, id) = create_api_key(&auth, &token, serde_json::json!({"name": "delete-me"})).await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
headers.insert("authorization".to_string(), format!("Bearer {}", token));
let delete_body = serde_json::json!({"id": id});
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/api-key/delete".to_string(),
headers,
Some(delete_body.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let body_str = String::from_utf8(response.body).unwrap();
let data: serde_json::Value = serde_json::from_str(&body_str).unwrap();
assert_eq!(data["status"], true);
let mut headers2 = HashMap::new();
headers2.insert("authorization".to_string(), format!("Bearer {}", token));
let list_request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Get,
"/api-key/list".to_string(),
headers2,
None,
HashMap::new(),
);
let list_response = auth.handle_request(list_request).await.unwrap();
let list_body: Vec<serde_json::Value> = serde_json::from_slice(&list_response.body).unwrap();
assert_eq!(list_body.len(), 0);
}
#[tokio::test]
async fn test_api_key_create_unauthenticated() {
let auth = create_test_auth_memory().await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
let body = serde_json::json!({"name": "no-auth"});
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/api-key/create".to_string(),
headers,
Some(body.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 401);
}
#[tokio::test]
async fn test_api_key_list_unauthenticated() {
let auth = create_test_auth_memory().await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Get,
"/api-key/list".to_string(),
HashMap::new(),
None,
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 401);
}
#[tokio::test]
async fn test_api_key_get_other_users_key() {
let (auth, _user_id, token1) = create_auth_with_apikey().await;
let (_key, id) = create_api_key(&auth, &token1, serde_json::json!({"name": "user1-key"})).await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
let signup_data = serde_json::json!({
"email": "user2@test.com",
"password": "password123",
"name": "Second User"
});
let signup_request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/sign-up/email".to_string(),
headers,
Some(signup_data.to_string().into_bytes()),
HashMap::new(),
);
let signup_resp = auth.handle_request(signup_request).await.unwrap();
let signup_body: serde_json::Value = serde_json::from_slice(&signup_resp.body).unwrap();
let token2 = signup_body["token"].as_str().unwrap();
let mut headers2 = HashMap::new();
headers2.insert("authorization".to_string(), format!("Bearer {}", token2));
let mut query = HashMap::new();
query.insert("id".to_string(), id.clone());
let get_request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Get,
"/api-key/get".to_string(),
headers2,
None,
query,
);
let response = auth.handle_request(get_request).await.unwrap();
assert_eq!(response.status, 404);
}
#[tokio::test]
async fn test_api_key_delete_other_users_key() {
let (auth, _user_id, token1) = create_auth_with_apikey().await;
let (_key, id) = create_api_key(&auth, &token1, serde_json::json!({"name": "user1-key"})).await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
let signup_data = serde_json::json!({
"email": "user3@test.com",
"password": "password123",
"name": "Third User"
});
let signup_request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/sign-up/email".to_string(),
headers,
Some(signup_data.to_string().into_bytes()),
HashMap::new(),
);
let signup_resp = auth.handle_request(signup_request).await.unwrap();
let signup_body: serde_json::Value = serde_json::from_slice(&signup_resp.body).unwrap();
let token2 = signup_body["token"].as_str().unwrap();
let mut headers2 = HashMap::new();
headers2.insert("content-type".to_string(), "application/json".to_string());
headers2.insert("authorization".to_string(), format!("Bearer {}", token2));
let delete_body = serde_json::json!({"id": id});
let delete_request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/api-key/delete".to_string(),
headers2,
Some(delete_body.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(delete_request).await.unwrap();
assert_eq!(response.status, 404);
}
#[tokio::test]
async fn test_api_key_update_other_users_key() {
let (auth, _user_id, token1) = create_auth_with_apikey().await;
let (_key, id) = create_api_key(&auth, &token1, serde_json::json!({"name": "user1-key"})).await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
let signup_data = serde_json::json!({
"email": "user4@test.com",
"password": "password123",
"name": "Fourth User"
});
let signup_request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/sign-up/email".to_string(),
headers,
Some(signup_data.to_string().into_bytes()),
HashMap::new(),
);
let signup_resp = auth.handle_request(signup_request).await.unwrap();
let signup_body: serde_json::Value = serde_json::from_slice(&signup_resp.body).unwrap();
let token2 = signup_body["token"].as_str().unwrap();
let mut headers2 = HashMap::new();
headers2.insert("content-type".to_string(), "application/json".to_string());
headers2.insert("authorization".to_string(), format!("Bearer {}", token2));
let update_body = serde_json::json!({
"id": id,
"name": "hijacked-name"
});
let update_request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Post,
"/api-key/update".to_string(),
headers2,
Some(update_body.to_string().into_bytes()),
HashMap::new(),
);
let response = auth.handle_request(update_request).await.unwrap();
assert_eq!(response.status, 404);
}
#[tokio::test]
async fn test_api_key_list_empty() {
let (auth, _user_id, token) = create_auth_with_apikey().await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert("authorization".to_string(), format!("Bearer {}", token));
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Get,
"/api-key/list".to_string(),
headers,
None,
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 200);
let data: Vec<serde_json::Value> = serde_json::from_slice(&response.body).unwrap();
assert_eq!(data.len(), 0);
}
#[tokio::test]
async fn test_api_key_get_missing_id() {
let (auth, _user_id, token) = create_auth_with_apikey().await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert("authorization".to_string(), format!("Bearer {}", token));
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Get,
"/api-key/get".to_string(),
headers,
None,
HashMap::new(),
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 400);
}
#[tokio::test]
async fn test_api_key_get_nonexistent() {
let (auth, _user_id, token) = create_auth_with_apikey().await;
use better_auth::types::AuthRequest;
use std::collections::HashMap;
let mut headers = HashMap::new();
headers.insert("authorization".to_string(), format!("Bearer {}", token));
let mut query = HashMap::new();
query.insert("id".to_string(), "nonexistent-id".to_string());
let request = AuthRequest::from_parts(
better_auth::types::HttpMethod::Get,
"/api-key/get".to_string(),
headers,
None,
query,
);
let response = auth.handle_request(request).await.unwrap();
assert_eq!(response.status, 404);
}
#[tokio::test]
async fn test_get_user_by_username_adapter() {
use better_auth::adapters::MemoryDatabaseAdapter;
use better_auth::types::CreateUser;
let db = MemoryDatabaseAdapter::new();
let create = CreateUser::new()
.with_email("dbtest@test.com")
.with_name("DB Test")
.with_username("db_user");
let user = db.create_user(create).await.unwrap();
let found = db.get_user_by_username("db_user").await.unwrap();
assert!(found.is_some());
assert_eq!(found.unwrap().id, user.id);
let not_found = db.get_user_by_username("no_user").await.unwrap();
assert!(not_found.is_none());
}