Files
music-metadata-manager/tests/requests/auth.rs
Gabriel Kaszewski 41afc170ba
Some checks failed
CI / Check Style (push) Has been cancelled
CI / Run Clippy (push) Has been cancelled
CI / Run Tests (push) Has been cancelled
init
2025-07-25 03:05:58 +02:00

372 lines
11 KiB
Rust

use insta::{assert_debug_snapshot, with_settings};
use loco_rs::testing::prelude::*;
use music_metadata_manager::{app::App, models::users};
use rstest::rstest;
use serial_test::serial;
use super::prepare_data;
// TODO: see how to dedup / extract this to app-local test utils
// not to framework, because that would require a runtime dep on insta
macro_rules! configure_insta {
($($expr:expr),*) => {
let mut settings = insta::Settings::clone_current();
settings.set_prepend_module_to_snapshot(false);
settings.set_snapshot_suffix("auth_request");
let _guard = settings.bind_to_scope();
};
}
#[tokio::test]
#[serial]
async fn can_register() {
configure_insta!();
request::<App, _, _>(|request, ctx| async move {
let email = "test@loco.com";
let payload = serde_json::json!({
"name": "loco",
"email": email,
"password": "12341234"
});
let response = request.post("/api/auth/register").json(&payload).await;
assert_eq!(
response.status_code(),
200,
"Register request should succeed"
);
let saved_user = users::Model::find_by_email(&ctx.db, email).await;
with_settings!({
filters => cleanup_user_model()
}, {
assert_debug_snapshot!(saved_user);
});
let deliveries = ctx.mailer.unwrap().deliveries();
assert_eq!(deliveries.count, 1, "Exactly one email should be sent");
// with_settings!({
// filters => cleanup_email()
// }, {
// assert_debug_snapshot!(ctx.mailer.unwrap().deliveries());
// });
})
.await;
}
#[rstest]
#[case("login_with_valid_password", "12341234")]
#[case("login_with_invalid_password", "invalid-password")]
#[tokio::test]
#[serial]
async fn can_login_with_verify(#[case] test_name: &str, #[case] password: &str) {
configure_insta!();
request::<App, _, _>(|request, ctx| async move {
let email = "test@loco.com";
let register_payload = serde_json::json!({
"name": "loco",
"email": email,
"password": "12341234"
});
//Creating a new user
let register_response = request
.post("/api/auth/register")
.json(&register_payload)
.await;
assert_eq!(
register_response.status_code(),
200,
"Register request should succeed"
);
let user = users::Model::find_by_email(&ctx.db, email).await.unwrap();
let email_verification_token = user
.email_verification_token
.expect("Email verification token should be generated");
request
.get(&format!("/api/auth/verify/{email_verification_token}"))
.await;
//verify user request
let response = request
.post("/api/auth/login")
.json(&serde_json::json!({
"email": email,
"password": password
}))
.await;
// Make sure email_verified_at is set
let user = users::Model::find_by_email(&ctx.db, email)
.await
.expect("Failed to find user by email");
assert!(
user.email_verified_at.is_some(),
"Expected the email to be verified, but it was not. User: {:?}",
user
);
with_settings!({
filters => cleanup_user_model()
}, {
assert_debug_snapshot!(test_name, (response.status_code(), response.text()));
});
})
.await;
}
#[tokio::test]
#[serial]
async fn can_login_without_verify() {
configure_insta!();
request::<App, _, _>(|request, _ctx| async move {
let email = "test@loco.com";
let password = "12341234";
let register_payload = serde_json::json!({
"name": "loco",
"email": email,
"password": password
});
//Creating a new user
let register_response = request
.post("/api/auth/register")
.json(&register_payload)
.await;
assert_eq!(
register_response.status_code(),
200,
"Register request should succeed"
);
//verify user request
let login_response = request
.post("/api/auth/login")
.json(&serde_json::json!({
"email": email,
"password": password
}))
.await;
assert_eq!(
login_response.status_code(),
200,
"Login request should succeed"
);
with_settings!({
filters => cleanup_user_model()
}, {
assert_debug_snapshot!(login_response.text());
});
})
.await;
}
#[tokio::test]
#[serial]
async fn can_reset_password() {
configure_insta!();
request::<App, _, _>(|request, ctx| async move {
let login_data = prepare_data::init_user_login(&request, &ctx).await;
let forgot_payload = serde_json::json!({
"email": login_data.user.email,
});
let forget_response = request.post("/api/auth/forgot").json(&forgot_payload).await;
assert_eq!(
forget_response.status_code(),
200,
"Forget request should succeed"
);
let user = users::Model::find_by_email(&ctx.db, &login_data.user.email)
.await
.expect("Failed to find user by email");
assert!(
user.reset_token.is_some(),
"Expected reset_token to be set, but it was None. User: {user:?}"
);
assert!(
user.reset_sent_at.is_some(),
"Expected reset_sent_at to be set, but it was None. User: {user:?}"
);
let new_password = "new-password";
let reset_payload = serde_json::json!({
"token": user.reset_token,
"password": new_password,
});
let reset_response = request.post("/api/auth/reset").json(&reset_payload).await;
assert_eq!(
reset_response.status_code(),
200,
"Reset password request should succeed"
);
let user = users::Model::find_by_email(&ctx.db, &user.email)
.await
.unwrap();
assert!(user.reset_token.is_none());
assert!(user.reset_sent_at.is_none());
assert_debug_snapshot!(reset_response.text());
let login_response = request
.post("/api/auth/login")
.json(&serde_json::json!({
"email": user.email,
"password": new_password
}))
.await;
assert_eq!(
login_response.status_code(),
200,
"Login request should succeed"
);
let deliveries = ctx.mailer.unwrap().deliveries();
assert_eq!(deliveries.count, 2, "Exactly one email should be sent");
// with_settings!({
// filters => cleanup_email()
// }, {
// assert_debug_snapshot!(deliveries.messages);
// });
})
.await;
}
#[tokio::test]
#[serial]
async fn can_get_current_user() {
configure_insta!();
request::<App, _, _>(|request, ctx| async move {
let user = prepare_data::init_user_login(&request, &ctx).await;
let (auth_key, auth_value) = prepare_data::auth_header(&user.token);
let response = request
.get("/api/auth/current")
.add_header(auth_key, auth_value)
.await;
assert_eq!(
response.status_code(),
200,
"Current request should succeed"
);
with_settings!({
filters => cleanup_user_model()
}, {
assert_debug_snapshot!((response.status_code(), response.text()));
});
})
.await;
}
#[tokio::test]
#[serial]
async fn can_auth_with_magic_link() {
configure_insta!();
request::<App, _, _>(|request, ctx| async move {
seed::<App>(&ctx).await.unwrap();
let payload = serde_json::json!({
"email": "user1@example.com",
});
let response = request.post("/api/auth/magic-link").json(&payload).await;
assert_eq!(
response.status_code(),
200,
"Magic link request should succeed"
);
let deliveries = ctx.mailer.unwrap().deliveries();
assert_eq!(deliveries.count, 1, "Exactly one email should be sent");
// let redact_token = format!("[a-zA-Z0-9]{{{}}}", users::MAGIC_LINK_LENGTH);
// with_settings!({
// filters => {
// let mut combined_filters = cleanup_email().clone();
// combined_filters.extend(vec![(r"(\\r\\n|=\\r\\n)", ""), (redact_token.as_str(), "[REDACT_TOKEN]") ]);
// combined_filters
// }
// }, {
// assert_debug_snapshot!(deliveries.messages);
// });
let user = users::Model::find_by_email(&ctx.db, "user1@example.com")
.await
.expect("User should be found");
let magic_link_token = user
.magic_link_token
.expect("Magic link token should be generated");
let magic_link_response = request
.get(&format!("/api/auth/magic-link/{magic_link_token}"))
.await;
assert_eq!(
magic_link_response.status_code(),
200,
"Magic link authentication should succeed"
);
with_settings!({
filters => cleanup_user_model()
}, {
assert_debug_snapshot!(magic_link_response.text());
});
})
.await;
}
#[tokio::test]
#[serial]
async fn can_reject_invalid_email() {
configure_insta!();
request::<App, _, _>(|request, _ctx| async move {
let invalid_email = "user1@temp-mail.com";
let payload = serde_json::json!({
"email": invalid_email,
});
let response = request.post("/api/auth/magic-link").json(&payload).await;
assert_eq!(
response.status_code(),
400,
"Expected request with invalid email '{invalid_email}' to be blocked, but it was allowed."
);
})
.await;
}
#[tokio::test]
#[serial]
async fn can_reject_invalid_magic_link_token() {
configure_insta!();
request::<App, _, _>(|request, ctx| async move {
seed::<App>(&ctx).await.unwrap();
let magic_link_response = request.get("/api/auth/magic-link/invalid-token").await;
assert_eq!(
magic_link_response.status_code(),
401,
"Magic link authentication should be rejected"
);
})
.await;
}