raisfast 0.2.23

The last backend you'll ever need. Rust-powered headless CMS with built-in blog, ecommerce, wallet, payment and 4 plugin engines.
use super::*;
use raisfast::DbDriver;

async fn setup_with_post() -> (axum::Router, AppState, String, String) {
    let (mut app, state) = test_app().await;
    let (int_id, id) = create_author(&state.pool).await;
    let tok = make_token(&id, int_id, raisfast::models::user::UserRole::Author);
    let slug = create_published_post(&mut app, &tok).await;
    (app, state, tok, slug)
}

#[tokio::test]
async fn guest_comment_success() {
    let (mut app, _, _, slug) = setup_with_post().await;
    let (status, body): (StatusCode, Value) = send(
        &mut app,
        post_json(
            &format!("/api/v1/posts/{slug}/comments"),
            json!({"content": "Nice!", "nickname": "Guest1", "email": "g@test.com"}),
        ),
    )
    .await;
    assert!(status.is_success(), "{status} {body:?}");
    assert_eq!(body["data"]["content"], "Nice!");
    assert_eq!(body["data"]["nickname"], "Guest1");
    assert!(body["data"]["created_by"].is_null());
}

#[tokio::test]
async fn guest_comment_requires_nickname() {
    let (mut app, _, _, slug) = setup_with_post().await;
    let (status, body): (StatusCode, Value) = send(
        &mut app,
        post_json(
            &format!("/api/v1/posts/{slug}/comments"),
            json!({"content": "no nick"}),
        ),
    )
    .await;
    assert_eq!(status, StatusCode::BAD_REQUEST);
    assert_eq!(body["code"], 40000);
}

#[tokio::test]
async fn authed_comment_success() {
    let (mut app, _, _, slug) = setup_with_post().await;
    let (tok, _) = register_and_login(&mut app, "cmtr@test.com", "cmtr", "Password123").await;
    let (status, body): (StatusCode, Value) = send(
        &mut app,
        post_json_auth(
            &format!("/api/v1/posts/{slug}/comments/authed"),
            json!({"content": "Auth comment"}),
            &tok,
        ),
    )
    .await;
    assert!(status.is_success(), "{status} {body:?}");
    assert_eq!(body["data"]["content"], "Auth comment");
}

#[tokio::test]
async fn nested_comment() {
    let (mut app, state, _, slug) = setup_with_post().await;
    let (_, b1): (StatusCode, Value) = send(
        &mut app,
        post_json(
            &format!("/api/v1/posts/{slug}/comments"),
            json!({"content": "Parent", "nickname": "G1"}),
        ),
    )
    .await;
    let pid_str = b1["data"]["id"].as_str().unwrap();
    let pid: i64 = pid_str.parse().unwrap();

    let approve_sql = format!(
        "UPDATE comments SET status = 'approved' WHERE id = {}",
        raisfast::db::Driver::ph(1)
    );
    sqlx::query(&approve_sql)
        .bind(pid)
        .execute(&state.pool)
        .await
        .unwrap();

    let (status, body): (StatusCode, Value) = send(
        &mut app,
        post_json(
            &format!("/api/v1/posts/{slug}/comments"),
            json!({"content": "Reply", "nickname": "G2", "parent_id": pid.to_string()}),
        ),
    )
    .await;
    assert!(status.is_success(), "{status} {body:?}");
    assert!(body["data"]["id"].is_string());
}

#[tokio::test]
async fn list_comments() {
    let (mut app, state, _, slug) = setup_with_post().await;
    let _: (StatusCode, Value) = send(
        &mut app,
        post_json(
            &format!("/api/v1/posts/{slug}/comments"),
            json!({"content": "C1", "nickname": "A"}),
        ),
    )
    .await;
    let _: (StatusCode, Value) = send(
        &mut app,
        post_json(
            &format!("/api/v1/posts/{slug}/comments"),
            json!({"content": "C2", "nickname": "B"}),
        ),
    )
    .await;

    sqlx::query("UPDATE comments SET status = 'approved' WHERE status = 'pending'")
        .execute(&state.pool)
        .await
        .unwrap();

    let (status, body): (StatusCode, Value) =
        send(&mut app, get_req(&format!("/api/v1/posts/{slug}/comments"))).await;
    assert!(status.is_success());
    let items = body["data"]["items"].as_array().unwrap();
    assert!(items.len() >= 2);
}

#[tokio::test]
async fn delete_own_comment() {
    let (mut app, _, _, slug) = setup_with_post().await;
    let (tok, _) = register_and_login(&mut app, "delc@test.com", "delcuser", "Password123").await;
    let (_, b): (StatusCode, Value) = send(
        &mut app,
        post_json_auth(
            &format!("/api/v1/posts/{slug}/comments/authed"),
            json!({"content": "mine"}),
            &tok,
        ),
    )
    .await;
    let cid = b["data"]["id"].as_str().unwrap().to_string();
    let (status, _): (StatusCode, Value) = send(
        &mut app,
        delete_auth(&format!("/api/v1/comments/{cid}"), &tok),
    )
    .await;
    assert!(status.is_success());
}

#[tokio::test]
async fn delete_not_owner_forbidden() {
    let (mut app, _, _, slug) = setup_with_post().await;
    let (t1, _) = register_and_login(&mut app, "own@test.com", "own", "Password123").await;
    let (t2, _) = register_and_login(&mut app, "str@test.com", "str", "Password123").await;
    let (_, b): (StatusCode, Value) = send(
        &mut app,
        post_json_auth(
            &format!("/api/v1/posts/{slug}/comments/authed"),
            json!({"content": "x"}),
            &t1,
        ),
    )
    .await;
    let cid = b["data"]["id"].as_str().unwrap().to_string();
    let (status, _): (StatusCode, Value) = send(
        &mut app,
        delete_auth(&format!("/api/v1/comments/{cid}"), &t2),
    )
    .await;
    assert_eq!(status, StatusCode::FORBIDDEN);
}

#[tokio::test]
async fn update_status_admin() {
    let (mut app, state, _, slug) = setup_with_post().await;
    let _: (StatusCode, Value) = send(
        &mut app,
        post_json(
            &format!("/api/v1/posts/{slug}/comments"),
            json!({"content": "mod me", "nickname": "G"}),
        ),
    )
    .await;

    let cid_i64: i64 = sqlx::query_scalar("SELECT id FROM comments WHERE content = 'mod me'")
        .fetch_one(&state.pool)
        .await
        .unwrap();
    let cid = cid_i64.to_string();

    let (admin_int_id, admin_id) = create_admin(&state.pool).await;
    let admin_tok = make_token(
        &admin_id,
        admin_int_id,
        raisfast::models::user::UserRole::Admin,
    );
    let (status, _): (StatusCode, Value) = send(
        &mut app,
        put_json_auth(
            &format!("/api/v1/comments/{cid}/status"),
            json!({"status": "approved"}),
            &admin_tok,
        ),
    )
    .await;
    assert!(status.is_success());
}

#[tokio::test]
async fn update_status_requires_admin() {
    let (mut app, _, _, _) = setup_with_post().await;
    let (tok, _) = register_and_login(&mut app, "na@test.com", "nauser", "Password123").await;
    let fake = "9999999999999";
    let (status, _): (StatusCode, Value) = send(
        &mut app,
        put_json_auth(
            &format!("/api/v1/comments/{fake}/status"),
            json!({"status": "approved"}),
            &tok,
        ),
    )
    .await;
    assert_eq!(status, StatusCode::FORBIDDEN);
}