init
Some checks failed
CI / Check Style (push) Has been cancelled
CI / Run Clippy (push) Has been cancelled
CI / Run Tests (push) Has been cancelled

This commit is contained in:
2025-07-25 03:05:58 +02:00
commit 41afc170ba
86 changed files with 9924 additions and 0 deletions

4
tests/mod.rs Normal file
View File

@@ -0,0 +1,4 @@
mod models;
mod requests;
mod tasks;
mod workers;

4
tests/models/mod.rs Normal file
View File

@@ -0,0 +1,4 @@
mod users;
mod music_libraries;
mod music_files;

View File

@@ -0,0 +1,31 @@
use music_metadata_manager::app::App;
use loco_rs::testing::prelude::*;
use serial_test::serial;
macro_rules! configure_insta {
($($expr:expr),*) => {
let mut settings = insta::Settings::clone_current();
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
};
}
#[tokio::test]
#[serial]
async fn test_model() {
configure_insta!();
let boot = boot_test::<App>().await.unwrap();
seed::<App>(&boot.app_context).await.unwrap();
// query your model, e.g.:
//
// let item = models::posts::Model::find_by_pid(
// &boot.app_context.db,
// "11111111-1111-1111-1111-111111111111",
// )
// .await;
// snapshot the result:
// assert_debug_snapshot!(item);
}

View File

@@ -0,0 +1,31 @@
use music_metadata_manager::app::App;
use loco_rs::testing::prelude::*;
use serial_test::serial;
macro_rules! configure_insta {
($($expr:expr),*) => {
let mut settings = insta::Settings::clone_current();
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
};
}
#[tokio::test]
#[serial]
async fn test_model() {
configure_insta!();
let boot = boot_test::<App>().await.unwrap();
seed::<App>(&boot.app_context).await.unwrap();
// query your model, e.g.:
//
// let item = models::posts::Model::find_by_pid(
// &boot.app_context.db,
// "11111111-1111-1111-1111-111111111111",
// )
// .await;
// snapshot the result:
// assert_debug_snapshot!(item);
}

View File

@@ -0,0 +1,23 @@
---
source: tests/models/users.rs
expression: res
---
Ok(
Model {
created_at: DATE,
updated_at: DATE,
id: ID
pid: PID,
email: "test@framework.com",
password: "PASSWORD",
api_key: "lo-PID",
name: "framework",
reset_token: None,
reset_sent_at: None,
email_verification_token: None,
email_verification_sent_at: None,
email_verified_at: None,
magic_link_token: None,
magic_link_expiration: None,
},
)

View File

@@ -0,0 +1,7 @@
---
source: tests/models/users.rs
expression: non_existing_user_results
---
Err(
EntityNotFound,
)

View File

@@ -0,0 +1,23 @@
---
source: tests/models/users.rs
expression: existing_user
---
Ok(
Model {
created_at: 2023-11-12T12:34:56.789+00:00,
updated_at: 2023-11-12T12:34:56.789+00:00,
id: 1,
pid: 11111111-1111-1111-1111-111111111111,
email: "user1@example.com",
password: "$argon2id$v=19$m=19456,t=2,p=1$ETQBx4rTgNAZhSaeYZKOZg$eYTdH26CRT6nUJtacLDEboP0li6xUwUF/q5nSlQ8uuc",
api_key: "lo-95ec80d7-cb60-4b70-9b4b-9ef74cb88758",
name: "user1",
reset_token: None,
reset_sent_at: None,
email_verification_token: None,
email_verification_sent_at: None,
email_verified_at: None,
magic_link_token: None,
magic_link_expiration: None,
},
)

View File

@@ -0,0 +1,7 @@
---
source: tests/models/users.rs
expression: non_existing_user_results
---
Err(
EntityNotFound,
)

View File

@@ -0,0 +1,23 @@
---
source: tests/models/users.rs
expression: existing_user
---
Ok(
Model {
created_at: 2023-11-12T12:34:56.789+00:00,
updated_at: 2023-11-12T12:34:56.789+00:00,
id: 1,
pid: 11111111-1111-1111-1111-111111111111,
email: "user1@example.com",
password: "$argon2id$v=19$m=19456,t=2,p=1$ETQBx4rTgNAZhSaeYZKOZg$eYTdH26CRT6nUJtacLDEboP0li6xUwUF/q5nSlQ8uuc",
api_key: "lo-95ec80d7-cb60-4b70-9b4b-9ef74cb88758",
name: "user1",
reset_token: None,
reset_sent_at: None,
email_verification_token: None,
email_verification_sent_at: None,
email_verified_at: None,
magic_link_token: None,
magic_link_expiration: None,
},
)

View File

@@ -0,0 +1,9 @@
---
source: tests/models/users.rs
expression: res
---
Err(
Custom(
"{\"email\":[{\"code\":\"invalid email\",\"message\":null}],\"name\":[{\"code\":\"length\",\"message\":\"Name must be at least 2 characters long.\"}]}",
),
)

View File

@@ -0,0 +1,7 @@
---
source: tests/models/users.rs
expression: new_user
---
Err(
EntityAlreadyExists,
)

360
tests/models/users.rs Normal file
View File

@@ -0,0 +1,360 @@
use chrono::{offset::Local, Duration};
use insta::assert_debug_snapshot;
use loco_rs::testing::prelude::*;
use music_metadata_manager::{
app::App,
models::users::{self, Model, RegisterParams},
};
use sea_orm::{ActiveModelTrait, ActiveValue, IntoActiveModel};
use serial_test::serial;
macro_rules! configure_insta {
($($expr:expr),*) => {
let mut settings = insta::Settings::clone_current();
settings.set_prepend_module_to_snapshot(false);
settings.set_snapshot_suffix("users");
let _guard = settings.bind_to_scope();
};
}
#[tokio::test]
#[serial]
async fn test_can_validate_model() {
configure_insta!();
let boot = boot_test::<App>()
.await
.expect("Failed to boot test application");
let invalid_user = users::ActiveModel {
name: ActiveValue::set("1".to_string()),
email: ActiveValue::set("invalid-email".to_string()),
..Default::default()
};
let res = invalid_user.insert(&boot.app_context.db).await;
assert_debug_snapshot!(res);
}
#[tokio::test]
#[serial]
async fn can_create_with_password() {
configure_insta!();
let boot = boot_test::<App>()
.await
.expect("Failed to boot test application");
let params = RegisterParams {
email: "test@framework.com".to_string(),
password: "1234".to_string(),
name: "framework".to_string(),
};
let res = Model::create_with_password(&boot.app_context.db, &params).await;
insta::with_settings!({
filters => cleanup_user_model()
}, {
assert_debug_snapshot!(res);
});
}
#[tokio::test]
#[serial]
async fn handle_create_with_password_with_duplicate() {
configure_insta!();
let boot = boot_test::<App>()
.await
.expect("Failed to boot test application");
seed::<App>(&boot.app_context)
.await
.expect("Failed to seed database");
let new_user = Model::create_with_password(
&boot.app_context.db,
&RegisterParams {
email: "user1@example.com".to_string(),
password: "1234".to_string(),
name: "framework".to_string(),
},
)
.await;
assert_debug_snapshot!(new_user);
}
#[tokio::test]
#[serial]
async fn can_find_by_email() {
configure_insta!();
let boot = boot_test::<App>()
.await
.expect("Failed to boot test application");
seed::<App>(&boot.app_context)
.await
.expect("Failed to seed database");
let existing_user = Model::find_by_email(&boot.app_context.db, "user1@example.com").await;
let non_existing_user_results =
Model::find_by_email(&boot.app_context.db, "un@existing-email.com").await;
assert_debug_snapshot!(existing_user);
assert_debug_snapshot!(non_existing_user_results);
}
#[tokio::test]
#[serial]
async fn can_find_by_pid() {
configure_insta!();
let boot = boot_test::<App>()
.await
.expect("Failed to boot test application");
seed::<App>(&boot.app_context)
.await
.expect("Failed to seed database");
let existing_user =
Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111").await;
let non_existing_user_results =
Model::find_by_pid(&boot.app_context.db, "23232323-2323-2323-2323-232323232323").await;
assert_debug_snapshot!(existing_user);
assert_debug_snapshot!(non_existing_user_results);
}
#[tokio::test]
#[serial]
async fn can_verification_token() {
configure_insta!();
let boot = boot_test::<App>()
.await
.expect("Failed to boot test application");
seed::<App>(&boot.app_context)
.await
.expect("Failed to seed database");
let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111")
.await
.expect("Failed to find user by PID");
assert!(
user.email_verification_sent_at.is_none(),
"Expected no email verification sent timestamp"
);
assert!(
user.email_verification_token.is_none(),
"Expected no email verification token"
);
let result = user
.into_active_model()
.set_email_verification_sent(&boot.app_context.db)
.await;
assert!(result.is_ok(), "Failed to set email verification sent");
let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111")
.await
.expect("Failed to find user by PID after setting verification sent");
assert!(
user.email_verification_sent_at.is_some(),
"Expected email verification sent timestamp to be present"
);
assert!(
user.email_verification_token.is_some(),
"Expected email verification token to be present"
);
}
#[tokio::test]
#[serial]
async fn can_set_forgot_password_sent() {
configure_insta!();
let boot = boot_test::<App>()
.await
.expect("Failed to boot test application");
seed::<App>(&boot.app_context)
.await
.expect("Failed to seed database");
let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111")
.await
.expect("Failed to find user by PID");
assert!(
user.reset_sent_at.is_none(),
"Expected no reset sent timestamp"
);
assert!(user.reset_token.is_none(), "Expected no reset token");
let result = user
.into_active_model()
.set_forgot_password_sent(&boot.app_context.db)
.await;
assert!(result.is_ok(), "Failed to set forgot password sent");
let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111")
.await
.expect("Failed to find user by PID after setting forgot password sent");
assert!(
user.reset_sent_at.is_some(),
"Expected reset sent timestamp to be present"
);
assert!(
user.reset_token.is_some(),
"Expected reset token to be present"
);
}
#[tokio::test]
#[serial]
async fn can_verified() {
configure_insta!();
let boot = boot_test::<App>()
.await
.expect("Failed to boot test application");
seed::<App>(&boot.app_context)
.await
.expect("Failed to seed database");
let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111")
.await
.expect("Failed to find user by PID");
assert!(
user.email_verified_at.is_none(),
"Expected email to be unverified"
);
let result = user
.into_active_model()
.verified(&boot.app_context.db)
.await;
assert!(result.is_ok(), "Failed to mark email as verified");
let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111")
.await
.expect("Failed to find user by PID after verification");
assert!(
user.email_verified_at.is_some(),
"Expected email to be verified"
);
}
#[tokio::test]
#[serial]
async fn can_reset_password() {
configure_insta!();
let boot = boot_test::<App>()
.await
.expect("Failed to boot test application");
seed::<App>(&boot.app_context)
.await
.expect("Failed to seed database");
let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111")
.await
.expect("Failed to find user by PID");
assert!(
user.verify_password("12341234"),
"Password verification failed for original password"
);
let result = user
.clone()
.into_active_model()
.reset_password(&boot.app_context.db, "new-password")
.await;
assert!(result.is_ok(), "Failed to reset password");
let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111")
.await
.expect("Failed to find user by PID after password reset");
assert!(
user.verify_password("new-password"),
"Password verification failed for new password"
);
}
#[tokio::test]
#[serial]
async fn magic_link() {
let boot = boot_test::<App>().await.unwrap();
seed::<App>(&boot.app_context).await.unwrap();
let user = Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111")
.await
.unwrap();
assert!(
user.magic_link_token.is_none(),
"Magic link token should be initially unset"
);
assert!(
user.magic_link_expiration.is_none(),
"Magic link expiration should be initially unset"
);
let create_result = user
.into_active_model()
.create_magic_link(&boot.app_context.db)
.await;
assert!(
create_result.is_ok(),
"Failed to create magic link: {:?}",
create_result.unwrap_err()
);
let updated_user =
Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111")
.await
.expect("Failed to refetch user after magic link creation");
assert!(
updated_user.magic_link_token.is_some(),
"Magic link token should be set after creation"
);
let magic_link_token = updated_user.magic_link_token.unwrap();
assert_eq!(
magic_link_token.len(),
users::MAGIC_LINK_LENGTH as usize,
"Magic link token length does not match expected length"
);
assert!(
updated_user.magic_link_expiration.is_some(),
"Magic link expiration should be set after creation"
);
let now = Local::now();
let should_expired_at = now + Duration::minutes(users::MAGIC_LINK_EXPIRATION_MIN.into());
let actual_expiration = updated_user.magic_link_expiration.unwrap();
assert!(
actual_expiration >= now,
"Magic link expiration should be in the future or now"
);
assert!(
actual_expiration <= should_expired_at,
"Magic link expiration exceeds expected maximum expiration time"
);
}

371
tests/requests/auth.rs Normal file
View File

@@ -0,0 +1,371 @@
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;
}

6
tests/requests/mod.rs Normal file
View File

@@ -0,0 +1,6 @@
mod auth;
mod prepare_data;
pub mod music_library;
pub mod music_file;
pub mod musicbrainz;

View File

@@ -0,0 +1,16 @@
use music_metadata_manager::app::App;
use loco_rs::testing::prelude::*;
use serial_test::serial;
#[tokio::test]
#[serial]
async fn can_get_music_files() {
request::<App, _, _>(|request, _ctx| async move {
let res = request.get("/api/music_files/").await;
assert_eq!(res.status_code(), 200);
// you can assert content like this:
// assert_eq!(res.text(), "content");
})
.await;
}

View File

@@ -0,0 +1,16 @@
use music_metadata_manager::app::App;
use loco_rs::testing::prelude::*;
use serial_test::serial;
#[tokio::test]
#[serial]
async fn can_get_music_libraries() {
request::<App, _, _>(|request, _ctx| async move {
let res = request.get("/api/music_libraries/").await;
assert_eq!(res.status_code(), 200);
// you can assert content like this:
// assert_eq!(res.text(), "content");
})
.await;
}

View File

@@ -0,0 +1,17 @@
use music_metadata_manager::app::App;
use loco_rs::testing::prelude::*;
use serial_test::serial;
#[tokio::test]
#[serial]
async fn can_get_musicbrainzs() {
request::<App, _, _>(|request, _ctx| async move {
let res = request.get("/api/musicbrainzs/").await;
assert_eq!(res.status_code(), 200);
// you can assert content like this:
// assert_eq!(res.text(), "content");
})
.await;
}

View File

@@ -0,0 +1,57 @@
use axum::http::{HeaderName, HeaderValue};
use loco_rs::{app::AppContext, TestServer};
use music_metadata_manager::{models::users, views::auth::LoginResponse};
const USER_EMAIL: &str = "test@loco.com";
const USER_PASSWORD: &str = "1234";
pub struct LoggedInUser {
pub user: users::Model,
pub token: String,
}
pub async fn init_user_login(request: &TestServer, ctx: &AppContext) -> LoggedInUser {
let register_payload = serde_json::json!({
"name": "loco",
"email": USER_EMAIL,
"password": USER_PASSWORD
});
//Creating a new user
request
.post("/api/auth/register")
.json(&register_payload)
.await;
let user = users::Model::find_by_email(&ctx.db, USER_EMAIL)
.await
.unwrap();
let verify_payload = serde_json::json!({
"token": user.email_verification_token,
});
request.post("/api/auth/verify").json(&verify_payload).await;
let response = request
.post("/api/auth/login")
.json(&serde_json::json!({
"email": USER_EMAIL,
"password": USER_PASSWORD
}))
.await;
let login_response: LoginResponse = serde_json::from_str(&response.text()).unwrap();
LoggedInUser {
user: users::Model::find_by_email(&ctx.db, USER_EMAIL)
.await
.unwrap(),
token: login_response.token,
}
}
pub fn auth_header(token: &str) -> (HeaderName, HeaderValue) {
let auth_header_value = HeaderValue::from_str(&format!("Bearer {}", &token)).unwrap();
(HeaderName::from_static("authorization"), auth_header_value)
}

View File

@@ -0,0 +1,5 @@
---
source: tests/requests/auth.rs
expression: magic_link_response.text()
---
"{\"token\":\"TOKEN\",\"pid\":\"PID\",\"name\":\"user1\",\"is_verified\":false}"

View File

@@ -0,0 +1,8 @@
---
source: tests/requests/auth.rs
expression: "(response.status_code(), response.text())"
---
(
200,
"{\"pid\":\"PID\",\"name\":\"loco\",\"email\":\"test@loco.com\"}",
)

View File

@@ -0,0 +1,5 @@
---
source: tests/requests/auth.rs
expression: login_response.text()
---
"{\"token\":\"TOKEN\",\"pid\":\"PID\",\"name\":\"loco\",\"is_verified\":false}"

View File

@@ -0,0 +1,27 @@
---
source: tests/requests/auth.rs
expression: saved_user
---
Ok(
Model {
created_at: DATE,
updated_at: DATE,
id: ID
pid: PID,
email: "test@loco.com",
password: "PASSWORD",
api_key: "lo-PID",
name: "loco",
reset_token: None,
reset_sent_at: None,
email_verification_token: Some(
"PID",
),
email_verification_sent_at: Some(
DATE,
),
email_verified_at: None,
magic_link_token: None,
magic_link_expiration: None,
},
)

View File

@@ -0,0 +1,5 @@
---
source: tests/requests/auth.rs
expression: "(reset_response.status_code(), reset_response.text())"
---
"null"

View File

@@ -0,0 +1,8 @@
---
source: tests/requests/auth.rs
expression: "(response.status_code(), response.text())"
---
(
401,
"{\"error\":\"unauthorized\",\"description\":\"You do not have permission to access this resource\"}",
)

View File

@@ -0,0 +1,8 @@
---
source: tests/requests/auth.rs
expression: "(response.status_code(), response.text())"
---
(
200,
"{\"token\":\"TOKEN\",\"pid\":\"PID\",\"name\":\"loco\",\"is_verified\":true}",
)

View File

@@ -0,0 +1,17 @@
use music_metadata_manager::app::App;
use loco_rs::{task, testing::prelude::*};
use loco_rs::boot::run_task;
use serial_test::serial;
#[tokio::test]
#[serial]
async fn test_can_run_create_user() {
let boot = boot_test::<App>().await.unwrap();
assert!(
run_task::<App>(&boot.app_context, Some(&"create_user".to_string()), &task::Vars::default())
.await
.is_ok()
);
}

3
tests/tasks/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod create_user;

3
tests/workers/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod scan_library_worker;

View File

@@ -0,0 +1,20 @@
use loco_rs::{bgworker::BackgroundWorker, testing::prelude::*};
use music_metadata_manager::{
app::App,
workers::scan_library_worker::{Worker, WorkerArgs},
};
use serial_test::serial;
#[tokio::test]
#[serial]
async fn test_run_scan_library_worker_worker() {
let boot = boot_test::<App>().await.unwrap();
// Execute the worker ensuring that it operates in 'ForegroundBlocking' mode, which prevents the addition of your worker to the background
assert!(
Worker::perform_later(&boot.app_context, WorkerArgs { library_id: 1 })
.await
.is_ok()
);
// Include additional assert validations after the execution of the worker
}