use super::*;
#[tokio::test]
async fn register_success() {
let (mut app, _) = test_app().await;
let (status, body): (StatusCode, Value) = send(
&mut app,
post_json(
"/api/v1/auth/register",
json!({"email": "reg@test.com", "username": "reguser", "password": "Password123"}),
),
)
.await;
assert!(status.is_success(), "{status} {body:?}");
assert_eq!(body["code"], 0);
assert_eq!(body["data"]["username"], "reguser");
assert_eq!(body["data"]["role"], "reader");
}
#[tokio::test]
async fn register_duplicate_email() {
let (mut app, _) = test_app().await;
let req_body = json!({"email": "dup@test.com", "username": "dup1", "password": "Password123"});
let (s, _): (StatusCode, Value) =
send(&mut app, post_json("/api/v1/auth/register", req_body)).await;
assert!(s.is_success());
let (status, body): (StatusCode, Value) = send(
&mut app,
post_json(
"/api/v1/auth/register",
json!({"email": "dup@test.com", "username": "dup2", "password": "Password123"}),
),
)
.await;
assert_eq!(status, StatusCode::CONFLICT);
assert_eq!(body["code"], 40900);
}
#[tokio::test]
async fn register_validation_errors() {
let (mut app, _) = test_app().await;
let cases = vec![
json!({"email": "bad", "username": "user", "password": "Password123"}),
json!({"email": "ok@test.com", "username": "a", "password": "Password123"}),
json!({"email": "ok@test.com", "username": "user", "password": "short"}),
json!({"email": "", "username": "", "password": ""}),
];
for case in cases {
let (status, body): (StatusCode, Value) =
send(&mut app, post_json("/api/v1/auth/register", case)).await;
assert_eq!(status, StatusCode::BAD_REQUEST, "expected 400: {body:?}");
assert_eq!(body["code"], 40000);
}
}
#[tokio::test]
async fn login_success() {
let (mut app, _) = test_app().await;
let (access, refresh) =
register_and_login(&mut app, "login@test.com", "loginuser", "Password123").await;
assert!(!access.is_empty());
assert!(!refresh.is_empty());
}
#[tokio::test]
async fn login_wrong_password() {
let (mut app, _) = test_app().await;
let _ = register_and_login(&mut app, "lwp@test.com", "lwpuser", "Password123").await;
let (status, body): (StatusCode, Value) = send(
&mut app,
post_json(
"/api/v1/auth/login",
json!({"email": "lwp@test.com", "password": "Wrong123"}),
),
)
.await;
assert_eq!(status, StatusCode::UNAUTHORIZED);
assert_eq!(body["code"], 40100);
}
#[tokio::test]
async fn login_nonexistent_user() {
let (mut app, _) = test_app().await;
let (status, _): (StatusCode, Value) = send(
&mut app,
post_json(
"/api/v1/auth/login",
json!({"email": "none@test.com", "password": "Password123"}),
),
)
.await;
assert_eq!(status, StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn refresh_token_success() {
let (mut app, _) = test_app().await;
let (_, refresh) =
register_and_login(&mut app, "refresh@test.com", "refreshuser", "Password123").await;
let (status, body): (StatusCode, Value) = send(
&mut app,
post_json("/api/v1/auth/refresh", json!({"refresh_token": refresh})),
)
.await;
assert!(status.is_success(), "{status} {body:?}");
assert_eq!(body["code"], 0);
assert!(body["data"]["access_token"].is_string());
assert!(body["data"]["refresh_token"].is_string());
}
#[tokio::test]
async fn refresh_token_invalid() {
let (mut app, _) = test_app().await;
let (status, _): (StatusCode, Value) = send(
&mut app,
post_json(
"/api/v1/auth/refresh",
json!({"refresh_token": "bad-token"}),
),
)
.await;
assert_eq!(status, StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn refresh_token_rotation() {
let (mut app, _) = test_app().await;
let (_, r1) = register_and_login(&mut app, "rot@test.com", "rotuser", "Password123").await;
let (_, body): (StatusCode, Value) = send(
&mut app,
post_json("/api/v1/auth/refresh", json!({"refresh_token": r1})),
)
.await;
let r2 = body["data"]["refresh_token"].as_str().unwrap().to_string();
let (s, _): (StatusCode, Value) = send(
&mut app,
post_json("/api/v1/auth/refresh", json!({"refresh_token": r1})),
)
.await;
assert_eq!(s, StatusCode::UNAUTHORIZED, "旧 token 应已失效");
let (s, _): (StatusCode, Value) = send(
&mut app,
post_json("/api/v1/auth/refresh", json!({"refresh_token": r2})),
)
.await;
assert!(s.is_success(), "新 token 应可用");
}
#[tokio::test]
async fn logout_success() {
let (mut app, _) = test_app().await;
let (access, _) = register_and_login(&mut app, "lo@test.com", "louser", "Password123").await;
let (status, body): (StatusCode, Value) = send(
&mut app,
post_json_auth("/api/v1/auth/logout", json!({}), &access),
)
.await;
assert!(status.is_success(), "{status} {body:?}");
assert_eq!(body["code"], 0);
}
#[tokio::test]
async fn logout_without_token() {
let (mut app, _) = test_app().await;
let (status, _): (StatusCode, Value) =
send(&mut app, post_json("/api/v1/auth/logout", json!({}))).await;
assert_eq!(status, StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn register_then_login_and_access_me() {
let (mut app, _) = test_app().await;
let (access, _) = register_and_login(
&mut app,
"lifecycle@test.com",
"lifecycleuser",
"Password123",
)
.await;
let (status, body) = send(&mut app, get_auth("/api/v1/users/me", &access)).await;
assert!(status.is_success(), "{status} {body:?}");
assert_eq!(body["data"]["username"], "lifecycleuser");
}
#[tokio::test]
async fn register_duplicate_username() {
let (mut app, _) = test_app().await;
let req_body =
json!({"email": "dupu1@test.com", "username": "dupuser", "password": "Password123"});
let (s, _): (StatusCode, Value) =
send(&mut app, post_json("/api/v1/auth/register", req_body)).await;
assert!(s.is_success());
let (status, body): (StatusCode, Value) = send(
&mut app,
post_json(
"/api/v1/auth/register",
json!({"email": "dupu2@test.com", "username": "dupuser", "password": "Password123"}),
),
)
.await;
assert_eq!(status, StatusCode::CONFLICT);
assert_eq!(body["code"], 40900);
}
#[tokio::test]
async fn refresh_token_after_password_change() {
let (mut app, _) = test_app().await;
let (access, refresh) =
register_and_login(&mut app, "rpc@test.com", "rpcuser", "OldPass123").await;
let (status, _): (StatusCode, Value) = send(
&mut app,
put_json_auth(
"/api/v1/users/me/password",
json!({"old_password": "OldPass123", "new_password": "NewPass456"}),
&access,
),
)
.await;
assert!(status.is_success());
let (status, _): (StatusCode, Value) = send(
&mut app,
post_json("/api/v1/auth/refresh", json!({"refresh_token": refresh})),
)
.await;
assert_eq!(
status,
StatusCode::UNAUTHORIZED,
"old refresh token should be invalidated after password change"
);
}
#[tokio::test]
async fn logout_invalidates_access() {
let (mut app, _) = test_app().await;
let (access, _) =
register_and_login(&mut app, "loinv@test.com", "loinvuser", "Password123").await;
let (status, _): (StatusCode, Value) = send(
&mut app,
post_json_auth("/api/v1/auth/logout", json!({}), &access),
)
.await;
assert!(status.is_success());
let (status, _): (StatusCode, Value) =
send(&mut app, get_auth("/api/v1/users/me", &access)).await;
assert!(
status.is_success(),
"JWT is stateless — still valid after logout"
);
}