raisfast 0.2.21

The last backend you'll ever need. Rust-powered headless CMS with built-in blog, ecommerce, wallet, payment and 4 plugin engines.
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"
    );
}