Link user id to song queue #159

Merged
kdeng00 merged 24 commits from link_user_id_to_song_queue into v0.2 2025-07-22 21:36:29 -04:00
7 changed files with 508 additions and 328 deletions
Generated
+1 -1
View File
@@ -752,7 +752,7 @@ dependencies = [
[[package]] [[package]]
name = "icarus" name = "icarus"
version = "0.1.92" version = "0.1.93"
dependencies = [ dependencies = [
"axum", "axum",
"common-multipart-rfc7578", "common-multipart-rfc7578",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "icarus" name = "icarus"
version = "0.1.92" version = "0.1.93"
edition = "2024" edition = "2024"
rust-version = "1.88" rust-version = "1.88"
+7 -8
View File
@@ -1,14 +1,9 @@
# Stage 1: Build the application # Stage 1: Build the application
# Use a specific Rust version for reproducibility. Choose one that matches your development environment.
# Using slim variant for smaller base image
FROM rust:1.88 as builder FROM rust:1.88 as builder
# Set the working directory inside the container # Set the working directory inside the container
WORKDIR /usr/src/app WORKDIR /usr/src/app
# Install build dependencies if needed (e.g., for certain crates like sqlx with native TLS)
# RUN apt-get update && apt-get install -y pkg-config libssl-dev
# Install build dependencies if needed (e.g., git for cloning) # Install build dependencies if needed (e.g., git for cloning)
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
pkg-config libssl3 \ pkg-config libssl3 \
@@ -16,10 +11,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
openssh-client git \ openssh-client git \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# << --- ADD HOST KEY HERE --- >> # Create .ssh/ directory for internal dependencies
# Replace 'yourgithost.com' with the actual hostname (e.g., github.com)
RUN mkdir -p -m 0700 ~/.ssh && \ RUN mkdir -p -m 0700 ~/.ssh && \
ssh-keyscan git.kundeng.us >> ~/.ssh/known_hosts echo "Host git.kundeng.us" >> ~/.ssh/config && \
echo " User git" >> ~/.ssh/config && \
chmod 600 ~/.ssh/config
# << --- ADD HOST KEY HERE --- >>
RUN ssh-keyscan git.kundeng.us >> ~/.ssh/known_hosts
# Copy Cargo manifests # Copy Cargo manifests
COPY Cargo.toml Cargo.lock ./ COPY Cargo.toml Cargo.lock ./
+2 -1
View File
@@ -6,7 +6,8 @@ CREATE TABLE IF NOT EXISTS "songQueue" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
filename TEXT NOT NULL, filename TEXT NOT NULL,
status TEXT CHECK (status IN ('pending', 'ready', 'processing', 'done')), status TEXT CHECK (status IN ('pending', 'ready', 'processing', 'done')),
data BYTEA NULL data BYTEA NULL,
user_id UUID NULL
); );
-- Table to store queued metadata -- Table to store queued metadata
+5
View File
@@ -4,6 +4,7 @@ pub mod song;
pub mod endpoints { pub mod endpoints {
pub const QUEUESONG: &str = "/api/v2/song/queue"; pub const QUEUESONG: &str = "/api/v2/song/queue";
pub const QUEUESONGLINKUSERID: &str = "/api/v2/song/queue/link";
pub const QUEUESONGDATA: &str = "/api/v2/song/queue/{id}"; pub const QUEUESONGDATA: &str = "/api/v2/song/queue/{id}";
pub const QUEUESONGUPDATE: &str = "/api/v2/song/queue/{id}"; pub const QUEUESONGUPDATE: &str = "/api/v2/song/queue/{id}";
pub const NEXTQUEUESONG: &str = "/api/v2/song/queue/next"; pub const NEXTQUEUESONG: &str = "/api/v2/song/queue/next";
@@ -17,3 +18,7 @@ pub mod endpoints {
pub const CREATESONG: &str = "/api/v2/song"; pub const CREATESONG: &str = "/api/v2/song";
pub const CREATECOVERART: &str = "/api/v2/coverart"; pub const CREATECOVERART: &str = "/api/v2/coverart";
} }
pub mod response {
pub const SUCCESSFUL: &str = "SUCCESSFUL";
}
+123 -30
View File
@@ -84,6 +84,14 @@ pub mod request {
pub song_queue_id: uuid::Uuid, pub song_queue_id: uuid::Uuid,
} }
} }
pub mod link_user_id {
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Request {
pub song_queue_id: uuid::Uuid,
pub user_id: uuid::Uuid,
}
}
} }
pub mod response { pub mod response {
@@ -142,6 +150,14 @@ pub mod response {
pub data: Vec<uuid::Uuid>, pub data: Vec<uuid::Uuid>,
} }
} }
pub mod link_user_id {
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct Response {
pub message: String,
pub data: Vec<uuid::Uuid>,
}
}
} }
// TODO: Might make a distinction between year and date in a song's tag at some point // TODO: Might make a distinction between year and date in a song's tag at some point
@@ -320,6 +336,7 @@ mod song_queue {
pub id: uuid::Uuid, pub id: uuid::Uuid,
pub filename: String, pub filename: String,
pub status: String, pub status: String,
pub user_id: uuid::Uuid,
} }
pub async fn insert( pub async fn insert(
@@ -393,7 +410,7 @@ mod song_queue {
FOR UPDATE SKIP LOCKED FOR UPDATE SKIP LOCKED
LIMIT 1 LIMIT 1
) )
RETURNING id, filename, status; RETURNING id, filename, status, user_id;
"#, "#,
) )
.bind(super::status::PROCESSING) .bind(super::status::PROCESSING)
@@ -405,20 +422,29 @@ mod song_queue {
}); });
match result { match result {
Ok(row) => Ok(SongQueue { Ok(row) => {
id: row let user_id_result = row.try_get("user_id");
.try_get("id") let song_queue = SongQueue {
.map_err(|_e| sqlx::Error::RowNotFound) id: row
.unwrap(), .try_get("id")
filename: row .map_err(|_e| sqlx::Error::RowNotFound)
.try_get("filename") .unwrap(),
.map_err(|_e| sqlx::Error::RowNotFound) filename: row
.unwrap(), .try_get("filename")
status: row .map_err(|_e| sqlx::Error::RowNotFound)
.try_get("status") .unwrap(),
.map_err(|_e| sqlx::Error::RowNotFound) status: row
.unwrap(), .try_get("status")
}), .map_err(|_e| sqlx::Error::RowNotFound)
.unwrap(),
user_id: match user_id_result {
Ok(id) => id,
Err(_) => uuid::Uuid::nil(),
},
};
Ok(song_queue)
}
Err(_err) => Err(sqlx::Error::RowNotFound), Err(_err) => Err(sqlx::Error::RowNotFound),
} }
} }
@@ -475,13 +501,40 @@ mod song_queue {
} }
} }
pub async fn link_user_id(
pool: &sqlx::PgPool,
id: &uuid::Uuid,
user_id: &uuid::Uuid,
) -> Result<uuid::Uuid, sqlx::Error> {
let result = sqlx::query(
r#"
UPDATE "songQueue" SET user_id = $1 WHERE id = $2 RETURNING user_id;
"#,
)
.bind(user_id)
.bind(id)
.fetch_one(pool)
.await
.map_err(|e| {
eprintln!("Error updating record {e}");
});
match result {
Ok(row) => Ok(row
.try_get("user_id")
.map_err(|_e| sqlx::Error::RowNotFound)
.unwrap()),
Err(_) => Err(sqlx::Error::RowNotFound),
}
}
pub async fn get_song_queue( pub async fn get_song_queue(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
id: &uuid::Uuid, id: &uuid::Uuid,
) -> Result<SongQueue, sqlx::Error> { ) -> Result<SongQueue, sqlx::Error> {
let result = sqlx::query( let result = sqlx::query(
r#" r#"
SELECT id, filename, status FROM "songQueue" WHERE id = $1 SELECT id, filename, status, user_id FROM "songQueue" WHERE id = $1
"#, "#,
) )
.bind(id) .bind(id)
@@ -492,20 +545,29 @@ mod song_queue {
}); });
match result { match result {
Ok(row) => Ok(SongQueue { Ok(row) => {
id: row let user_id_result = row.try_get("user_id");
.try_get("id") let song_queue = SongQueue {
.map_err(|_e| sqlx::Error::RowNotFound) id: row
.unwrap(), .try_get("id")
filename: row .map_err(|_e| sqlx::Error::RowNotFound)
.try_get("filename") .unwrap(),
.map_err(|_e| sqlx::Error::RowNotFound) filename: row
.unwrap(), .try_get("filename")
status: row .map_err(|_e| sqlx::Error::RowNotFound)
.try_get("status") .unwrap(),
.map_err(|_e| sqlx::Error::RowNotFound) status: row
.unwrap(), .try_get("status")
}), .map_err(|_e| sqlx::Error::RowNotFound)
.unwrap(),
user_id: match user_id_result {
Ok(id) => id,
Err(_) => uuid::Uuid::nil(),
},
};
Ok(song_queue)
}
Err(_err) => Err(sqlx::Error::RowNotFound), Err(_err) => Err(sqlx::Error::RowNotFound),
} }
} }
@@ -616,6 +678,37 @@ pub mod endpoint {
(StatusCode::OK, Json(response)) (StatusCode::OK, Json(response))
} }
pub async fn link_user_id(
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
axum::Json(payload): axum::Json<super::request::link_user_id::Request>,
) -> (
axum::http::StatusCode,
axum::Json<super::response::link_user_id::Response>,
) {
let mut response = super::response::link_user_id::Response::default();
match super::song_queue::get_song_queue(&pool, &payload.song_queue_id).await {
Ok(song_queue) => {
match super::song_queue::link_user_id(&pool, &song_queue.id, &payload.user_id).await
{
Ok(user_id) => {
response.message = String::from(crate::callers::response::SUCCESSFUL);
response.data.push(user_id);
(axum::http::StatusCode::OK, axum::Json(response))
}
Err(err) => {
response.message = err.to_string();
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
}
}
}
Err(err) => {
response.message = err.to_string();
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
}
}
}
pub async fn fetch_queue_song( pub async fn fetch_queue_song(
axum::Extension(pool): axum::Extension<sqlx::PgPool>, axum::Extension(pool): axum::Extension<sqlx::PgPool>,
) -> ( ) -> (
+369 -287
View File
@@ -59,6 +59,10 @@ pub mod init {
crate::callers::endpoints::QUEUESONG, crate::callers::endpoints::QUEUESONG,
patch(crate::callers::song::endpoint::update_song_queue_status), patch(crate::callers::song::endpoint::update_song_queue_status),
) )
.route(
crate::callers::endpoints::QUEUESONGLINKUSERID,
patch(crate::callers::song::endpoint::link_user_id),
)
.route( .route(
crate::callers::endpoints::QUEUESONGDATA, crate::callers::endpoints::QUEUESONGDATA,
get(crate::callers::song::endpoint::download_flac), get(crate::callers::song::endpoint::download_flac),
@@ -237,6 +241,7 @@ mod tests {
} }
} }
// TODO: Put the *_req() functions in their own module
async fn song_queue_req( async fn song_queue_req(
app: &axum::Router, app: &axum::Router,
) -> Result<axum::response::Response, std::convert::Infallible> { ) -> Result<axum::response::Response, std::convert::Infallible> {
@@ -256,6 +261,26 @@ mod tests {
app.clone().oneshot(req).await app.clone().oneshot(req).await
} }
async fn song_queue_link_req(
app: &axum::Router,
song_queue_id: &uuid::Uuid,
user_id: &uuid::Uuid,
) -> Result<axum::response::Response, std::convert::Infallible> {
let payload = serde_json::json!({
"song_queue_id": song_queue_id,
"user_id": user_id
});
let req = axum::http::Request::builder()
.method(axum::http::Method::PATCH)
.uri(crate::callers::endpoints::QUEUESONGLINKUSERID)
.header(axum::http::header::CONTENT_TYPE, "application/json")
.body(axum::body::Body::from(payload.to_string()))
.unwrap();
app.clone().oneshot(req).await
}
async fn fetch_queue_req( async fn fetch_queue_req(
app: &axum::Router, app: &axum::Router,
) -> Result<axum::response::Response, std::convert::Infallible> { ) -> Result<axum::response::Response, std::convert::Infallible> {
@@ -380,6 +405,7 @@ mod tests {
async fn create_song_req( async fn create_song_req(
app: &axum::Router, app: &axum::Router,
song_queue_id: &uuid::Uuid, song_queue_id: &uuid::Uuid,
user_id: &uuid::Uuid,
) -> Result<axum::response::Response, std::convert::Infallible> { ) -> Result<axum::response::Response, std::convert::Infallible> {
let payload = serde_json::json!({ let payload = serde_json::json!({
"title": "Power of Soul", "title": "Power of Soul",
@@ -394,7 +420,7 @@ mod tests {
"disc_count": 1, "disc_count": 1,
"duration": 330, "duration": 330,
"audio_type": "flac", "audio_type": "flac",
"user_id": "d6e159c1-9648-4c85-81e5-52f502ff53e4", "user_id": user_id,
"song_queue_id": song_queue_id "song_queue_id": song_queue_id
}); });
@@ -451,7 +477,7 @@ mod tests {
// Flow for queueing song // Flow for queueing song
pub async fn queue_song_flow( pub async fn queue_song_flow(
app: &axum::Router, app: &axum::Router,
) -> Result<axum::response::Response, std::convert::Infallible> { ) -> Result<(axum::response::Response, uuid::Uuid), std::convert::Infallible> {
match super::song_queue_req(&app).await { match super::song_queue_req(&app).await {
Ok(response) => { Ok(response) => {
let resp = let resp =
@@ -462,21 +488,40 @@ mod tests {
let song_queue_id = resp.data[0]; let song_queue_id = resp.data[0];
assert_eq!(false, song_queue_id.is_nil(), "Should not be empty"); assert_eq!(false, song_queue_id.is_nil(), "Should not be empty");
match super::queue_metadata_req(&app, &resp.data[0]).await { let user_id = uuid::Uuid::new_v4();
// match super::get_resp_data::<crate::callers::song::response::link_user_id::Response>(response).await {
match super::song_queue_link_req(&app, &song_queue_id, &user_id).await {
Ok(response) => { Ok(response) => {
let resp = super::get_resp_data::< let resp = super::get_resp_data::<
crate::callers::song::response::Response, crate::callers::song::response::link_user_id::Response,
>(response) >(response)
.await; .await;
assert_eq!(false, resp.data.is_empty(), "Should not be empty"); assert_eq!(
false,
resp.data.is_empty(),
"The response should not be empty"
);
let id = resp.data[0]; match super::queue_metadata_req(&app, &song_queue_id).await {
Ok(response) => {
let resp = super::get_resp_data::<
crate::callers::song::response::Response,
>(response)
.await;
assert_eq!(false, resp.data.is_empty(), "Should not be empty");
match super::fetch_metadata_queue_req(&app, &id).await { let id = resp.data[0];
Ok(response) => Ok(response),
Err(err) => Err(err), match super::fetch_metadata_queue_req(&app, &id).await {
Ok(response) => Ok((response, user_id)),
Err(err) => Err(err),
}
}
} }
} }
Err(err) => Err(err),
} }
} }
Err(err) => Err(err), Err(err) => Err(err),
@@ -533,7 +578,7 @@ mod tests {
app: &axum::Router, app: &axum::Router,
) -> Result<(axum::response::Response, uuid::Uuid), std::convert::Infallible> { ) -> Result<(axum::response::Response, uuid::Uuid), std::convert::Infallible> {
match queue_song_flow(&app).await { match queue_song_flow(&app).await {
Ok(song_response) => { Ok((song_response, user_id)) => {
let resp = super::get_resp_data::< let resp = super::get_resp_data::<
crate::callers::metadata::response::fetch_metadata::Response, crate::callers::metadata::response::fetch_metadata::Response,
>(song_response) >(song_response)
@@ -541,7 +586,7 @@ mod tests {
assert_eq!(false, resp.data.is_empty(), "Data should not be empty"); assert_eq!(false, resp.data.is_empty(), "Data should not be empty");
let song_queue_id = resp.data[0].song_queue_id; let song_queue_id = resp.data[0].song_queue_id;
match super::create_song_req(&app, &song_queue_id).await { match super::create_song_req(&app, &song_queue_id, &user_id).await {
Ok(response) => { Ok(response) => {
let resp = super::get_resp_data::< let resp = super::get_resp_data::<
crate::callers::song::response::create_metadata::Response, crate::callers::song::response::create_metadata::Response,
@@ -586,12 +631,14 @@ mod tests {
} }
} }
// TODO: Put this in a util module
pub async fn resp_to_bytes( pub async fn resp_to_bytes(
response: axum::response::Response, response: axum::response::Response,
) -> Result<axum::body::Bytes, axum::Error> { ) -> Result<axum::body::Bytes, axum::Error> {
axum::body::to_bytes(response.into_body(), usize::MAX).await axum::body::to_bytes(response.into_body(), usize::MAX).await
} }
// TODO: Put this in a util module
pub async fn get_resp_data<Data>(response: axum::response::Response) -> Data pub async fn get_resp_data<Data>(response: axum::response::Response) -> Data
where where
Data: for<'a> serde::Deserialize<'a>, Data: for<'a> serde::Deserialize<'a>,
@@ -600,6 +647,7 @@ mod tests {
serde_json::from_slice(&body).unwrap() serde_json::from_slice(&body).unwrap()
} }
// TODO: Change the name of the function to be more expressive and put into it's own module
pub mod payload_data { pub mod payload_data {
pub async fn queue_metadata_payload_data(song_queue_id: &uuid::Uuid) -> serde_json::Value { pub async fn queue_metadata_payload_data(song_queue_id: &uuid::Uuid) -> serde_json::Value {
serde_json::json!( serde_json::json!(
@@ -655,69 +703,225 @@ mod tests {
let _ = db_mgr::drop_database(&tm_pool, &db_name).await; let _ = db_mgr::drop_database(&tm_pool, &db_name).await;
} }
mod special_area { #[tokio::test]
async fn test_song_queue_link_user_id() {
let tm_pool = db_mgr::get_pool().await.unwrap();
let db_name = db_mgr::generate_db_name().await;
use super::*; match db_mgr::create_database(&tm_pool, &db_name).await {
Ok(_) => {
println!("Success");
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
}
use std::io::Write; let pool = db_mgr::connect_to_db(&db_name).await.unwrap();
db::migrations(&pool).await;
#[tokio::test] let app = init::app(pool).await;
async fn test_song_fetch_queue_item() {
let tm_pool = db_mgr::get_pool().await.unwrap();
let db_name = db_mgr::generate_db_name().await;
match db_mgr::create_database(&tm_pool, &db_name).await { match song_queue_req(&app).await {
Ok(_) => { Ok(response) => {
println!("Success"); let resp =
} get_resp_data::<crate::callers::song::response::Response>(response).await;
Err(err) => { assert_eq!(false, resp.data.is_empty(), "Should not be empty");
assert!(false, "Error: {:?}", err); assert_eq!(false, resp.data[0].is_nil(), "Should not be empty");
let song_queue_id = &resp.data[0];
let user_id = uuid::Uuid::new_v4();
println!("User Id: {user_id:?}");
match song_queue_link_req(&app, &song_queue_id, &user_id).await {
Ok(response) => {
let resp = get_resp_data::<
crate::callers::song::response::link_user_id::Response,
>(response)
.await;
let collected_user_id = &resp.data[0];
assert!(
!collected_user_id.is_nil(),
"Collected user id should not be nil {collected_user_id:?}"
);
assert_eq!(
user_id, *collected_user_id,
"User Id is different. First {user_id:?} Second {collected_user_id:?}"
);
}
Err(err) => {
assert!(
false,
"Error: {err:?} songQueue Id {song_queue_id:?} user id {user_id:?}"
);
}
} }
} }
Err(err) => {
assert!(false, "Error: {:?}", err);
}
};
let pool = db_mgr::connect_to_db(&db_name).await.unwrap(); let _ = db_mgr::drop_database(&tm_pool, &db_name).await;
db::migrations(&pool).await; }
let app = init::app(pool).await; #[tokio::test]
async fn test_song_fetch_queue_item() {
let tm_pool = db_mgr::get_pool().await.unwrap();
let db_name = db_mgr::generate_db_name().await;
match sequence_flow::queue_song_and_coverart_flow(&app).await { match db_mgr::create_database(&tm_pool, &db_name).await {
Ok((resp_one, song_queue_id)) => { Ok(_) => {
let resp = get_resp_data::< println!("Success");
crate::callers::coverart::response::fetch_coverart_no_data::Response, }
>(resp_one) Err(err) => {
.await; assert!(false, "Error: {:?}", err);
assert_eq!(false, resp.data.is_empty(), "Should not be empty"); }
}
let _resp_coverart_queue_id = resp.data[0].id; let pool = db_mgr::connect_to_db(&db_name).await.unwrap();
db::migrations(&pool).await;
let old = crate::callers::song::status::PENDING; let app = init::app(pool).await;
let target_status = crate::callers::song::status::READY;
match update_song_queue_status_req(&app, &song_queue_id).await { match sequence_flow::queue_song_and_coverart_flow(&app).await {
Ok(response) => { Ok((resp_one, song_queue_id)) => {
let resp = get_resp_data::< let resp = get_resp_data::<
crate::callers::song::response::update_status::Response, crate::callers::coverart::response::fetch_coverart_no_data::Response,
>(response) >(resp_one)
.await; .await;
assert_eq!(false, resp.data.is_empty(), "Should not be empty"); assert_eq!(false, resp.data.is_empty(), "Should not be empty");
let changed_status = &resp.data[0];
assert_eq!( let _resp_coverart_queue_id = resp.data[0].id;
*old, changed_status.old_status,
"Old status does not match" let old = crate::callers::song::status::PENDING;
); let target_status = crate::callers::song::status::READY;
assert_eq!(
target_status, changed_status.new_status, match update_song_queue_status_req(&app, &song_queue_id).await {
"New status does not match" Ok(response) => {
let resp = get_resp_data::<
crate::callers::song::response::update_status::Response,
>(response)
.await;
assert_eq!(false, resp.data.is_empty(), "Should not be empty");
let changed_status = &resp.data[0];
assert_eq!(*old, changed_status.old_status, "Old status does not match");
assert_eq!(
target_status, changed_status.new_status,
"New status does not match"
);
match fetch_queue_req(&app).await {
Ok(response) => {
let resp = get_resp_data::<
crate::callers::song::response::fetch_queue_song::Response,
>(response)
.await;
assert_eq!(false, resp.data.is_empty(), "Should not be empty");
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
}
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
}
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
};
let _ = db_mgr::drop_database(&tm_pool, &db_name).await;
}
#[tokio::test]
async fn test_update_song_from_queue() {
let tm_pool = db_mgr::get_pool().await.unwrap();
let db_name = db_mgr::generate_db_name().await;
match db_mgr::create_database(&tm_pool, &db_name).await {
Ok(_) => {
println!("Success");
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
}
let pool = db_mgr::connect_to_db(&db_name).await.unwrap();
db::migrations(&pool).await;
let app = init::app(pool).await;
// Send request
match song_queue_req(&app).await {
Ok(response) => {
let resp =
get_resp_data::<crate::callers::song::response::Response>(response).await;
assert_eq!(false, resp.data.is_empty(), "Should not be empty");
assert_eq!(false, resp.data[0].is_nil(), "Should not be empty");
let id = &resp.data[0];
match fetch_queue_data_req(&app, &id).await {
Ok(response) => match resp_to_bytes(response).await {
Ok(bytes) => {
assert_eq!(false, bytes.is_empty(), "Queued data should not be empty");
let temp_file =
tempfile::tempdir().expect("Could not create test directory");
let test_dir = String::from(temp_file.path().to_str().unwrap());
let new_file = format!("{}/new_file.flac", test_dir);
let mut file = std::fs::File::create(&new_file).unwrap();
file.write_all(&bytes).unwrap();
let mut form = MultipartForm::default();
let _ = form.add_file("flac", new_file);
// Create request
let content_type = form.content_type();
let body = MultipartBody::from(form);
let raw_uri = String::from(crate::callers::endpoints::QUEUESONGUPDATE);
let end_index = raw_uri.len() - 5;
let uri = format!(
"{}/{}",
(&raw_uri[..end_index]).to_string(),
id.to_string()
); );
match fetch_queue_req(&app).await { match app
.clone()
.oneshot(
axum::http::Request::builder()
.method(axum::http::Method::PATCH)
.uri(uri)
.header(axum::http::header::CONTENT_TYPE, content_type)
.body(axum::body::Body::from_stream(body))
.unwrap(),
)
.await
{
Ok(response) => { Ok(response) => {
let resp = get_resp_data::< let resp = get_resp_data::<
crate::callers::song::response::fetch_queue_song::Response, crate::callers::song::response::update_song_queue::Response,
>(response) >(response)
.await; .await;
assert_eq!(false, resp.data.is_empty(), "Should not be empty"); assert_eq!(false, resp.data.is_empty(), "Should not be empty");
let updated_song_queued_id = resp.data[0];
assert_eq!(
updated_song_queued_id, *id,
"Song queue Id should match, but they don't. {:?} {:?}",
updated_song_queued_id, id
);
} }
Err(err) => { Err(err) => {
assert!(false, "Error: {:?}", err); assert!(false, "Error: {:?}", err);
@@ -727,247 +931,125 @@ mod tests {
Err(err) => { Err(err) => {
assert!(false, "Error: {:?}", err); assert!(false, "Error: {:?}", err);
} }
},
Err(err) => {
assert!(false, "Error: {:?}", err);
} }
} }
Err(err) => { }
assert!(false, "Error: {:?}", err); Err(err) => {
} assert!(false, "Error: {:?}", err);
}; }
};
let _ = db_mgr::drop_database(&tm_pool, &db_name).await; let _ = db_mgr::drop_database(&tm_pool, &db_name).await;
}
#[tokio::test]
async fn test_song_fetch_queue_data() {
let tm_pool = db_mgr::get_pool().await.unwrap();
let db_name = db_mgr::generate_db_name().await;
match db_mgr::create_database(&tm_pool, &db_name).await {
Ok(_) => {
println!("Success");
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
} }
#[tokio::test] let pool = db_mgr::connect_to_db(&db_name).await.unwrap();
async fn test_update_song_from_queue() { db::migrations(&pool).await;
let tm_pool = db_mgr::get_pool().await.unwrap();
let db_name = db_mgr::generate_db_name().await;
match db_mgr::create_database(&tm_pool, &db_name).await { let app = init::app(pool).await;
Ok(_) => {
println!("Success");
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
}
let pool = db_mgr::connect_to_db(&db_name).await.unwrap(); // Send request
db::migrations(&pool).await; match song_queue_req(&app).await {
Ok(response) => {
let resp =
get_resp_data::<crate::callers::song::response::Response>(response).await;
assert_eq!(false, resp.data.is_empty(), "Should not be empty");
assert_eq!(false, resp.data[0].is_nil(), "Should not be empty");
let id = resp.data[0];
let app = init::app(pool).await; match fetch_queue_data_req(&app, &id).await {
Ok(response) => match resp_to_bytes(response).await {
// Send request Ok(bytes) => {
match song_queue_req(&app).await { assert_eq!(false, bytes.is_empty(), "Queued data should not be empty");
Ok(response) => {
let resp =
get_resp_data::<crate::callers::song::response::Response>(response).await;
assert_eq!(false, resp.data.is_empty(), "Should not be empty");
assert_eq!(false, resp.data[0].is_nil(), "Should not be empty");
let id = &resp.data[0];
match fetch_queue_data_req(&app, &id).await {
Ok(response) => match resp_to_bytes(response).await {
Ok(bytes) => {
assert_eq!(
false,
bytes.is_empty(),
"Queued data should not be empty"
);
let temp_file =
tempfile::tempdir().expect("Could not create test directory");
let test_dir = String::from(temp_file.path().to_str().unwrap());
let new_file = format!("{}/new_file.flac", test_dir);
let mut file = std::fs::File::create(&new_file).unwrap();
file.write_all(&bytes).unwrap();
let mut form = MultipartForm::default();
let _ = form.add_file("flac", new_file);
// Create request
let content_type = form.content_type();
let body = MultipartBody::from(form);
let raw_uri =
String::from(crate::callers::endpoints::QUEUESONGUPDATE);
let end_index = raw_uri.len() - 5;
let uri = format!(
"{}/{}",
(&raw_uri[..end_index]).to_string(),
id.to_string()
);
match app
.clone()
.oneshot(
axum::http::Request::builder()
.method(axum::http::Method::PATCH)
.uri(uri)
.header(axum::http::header::CONTENT_TYPE, content_type)
.body(axum::body::Body::from_stream(body))
.unwrap(),
)
.await
{
Ok(response) => {
let resp = get_resp_data::<
crate::callers::song::response::update_song_queue::Response,
>(response)
.await;
assert_eq!(
false,
resp.data.is_empty(),
"Should not be empty"
);
let updated_song_queued_id = resp.data[0];
assert_eq!(
updated_song_queued_id, *id,
"Song queue Id should match, but they don't. {:?} {:?}",
updated_song_queued_id, id
);
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
}
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
},
Err(err) => {
assert!(false, "Error: {:?}", err);
}
}
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
};
let _ = db_mgr::drop_database(&tm_pool, &db_name).await;
}
#[tokio::test]
async fn test_song_fetch_queue_data() {
let tm_pool = db_mgr::get_pool().await.unwrap();
let db_name = db_mgr::generate_db_name().await;
match db_mgr::create_database(&tm_pool, &db_name).await {
Ok(_) => {
println!("Success");
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
}
let pool = db_mgr::connect_to_db(&db_name).await.unwrap();
db::migrations(&pool).await;
let app = init::app(pool).await;
// Send request
match song_queue_req(&app).await {
Ok(response) => {
let resp =
get_resp_data::<crate::callers::song::response::Response>(response).await;
assert_eq!(false, resp.data.is_empty(), "Should not be empty");
assert_eq!(false, resp.data[0].is_nil(), "Should not be empty");
let id = resp.data[0];
match fetch_queue_data_req(&app, &id).await {
Ok(response) => match resp_to_bytes(response).await {
Ok(bytes) => {
assert_eq!(
false,
bytes.is_empty(),
"Queued data should not be empty"
);
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
},
Err(err) => {
assert!(false, "Error: {:?}", err);
}
}
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
};
let _ = db_mgr::drop_database(&tm_pool, &db_name).await;
}
#[tokio::test]
async fn test_song_queue_update_status() {
let tm_pool = db_mgr::get_pool().await.unwrap();
let db_name = db_mgr::generate_db_name().await;
match db_mgr::create_database(&tm_pool, &db_name).await {
Ok(_) => {
println!("Success");
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
}
let pool = db_mgr::connect_to_db(&db_name).await.unwrap();
db::migrations(&pool).await;
let app = init::app(pool).await;
match sequence_flow::queue_song_and_coverart_flow(&app).await {
Ok((resp_one, song_queue_id)) => {
let resp = get_resp_data::<
crate::callers::coverart::response::fetch_coverart_no_data::Response,
>(resp_one)
.await;
assert_eq!(false, resp.data.is_empty(), "Should not be empty");
let _resp_coverart_queue_id = resp.data[0].id;
let old = crate::callers::song::status::PENDING;
let done = crate::callers::song::status::READY;
match update_song_queue_status_req(&app, &song_queue_id).await {
Ok(response) => {
let resp = get_resp_data::<
crate::callers::song::response::update_status::Response,
>(response)
.await;
assert_eq!(false, resp.data.is_empty(), "Should not be empty");
let changed_status = &resp.data[0];
assert_eq!(
*old, changed_status.old_status,
"Old status does not match"
);
assert_eq!(
done, changed_status.new_status,
"New status does not match"
);
} }
Err(err) => { Err(err) => {
assert!(false, "Error: {:?}", err); assert!(false, "Error: {:?}", err);
} }
},
Err(err) => {
assert!(false, "Error: {:?}", err);
} }
} }
Err(err) => { }
assert!(false, "Error: {:?}", err); Err(err) => {
assert!(false, "Error: {:?}", err);
}
};
let _ = db_mgr::drop_database(&tm_pool, &db_name).await;
}
#[tokio::test]
async fn test_song_queue_update_status() {
let tm_pool = db_mgr::get_pool().await.unwrap();
let db_name = db_mgr::generate_db_name().await;
match db_mgr::create_database(&tm_pool, &db_name).await {
Ok(_) => {
println!("Success");
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
}
let pool = db_mgr::connect_to_db(&db_name).await.unwrap();
db::migrations(&pool).await;
let app = init::app(pool).await;
match sequence_flow::queue_song_and_coverart_flow(&app).await {
Ok((resp_one, song_queue_id)) => {
let resp = get_resp_data::<
crate::callers::coverart::response::fetch_coverart_no_data::Response,
>(resp_one)
.await;
assert_eq!(false, resp.data.is_empty(), "Should not be empty");
let _resp_coverart_queue_id = resp.data[0].id;
let old = crate::callers::song::status::PENDING;
let done = crate::callers::song::status::READY;
match update_song_queue_status_req(&app, &song_queue_id).await {
Ok(response) => {
let resp = get_resp_data::<
crate::callers::song::response::update_status::Response,
>(response)
.await;
assert_eq!(false, resp.data.is_empty(), "Should not be empty");
let changed_status = &resp.data[0];
assert_eq!(*old, changed_status.old_status, "Old status does not match");
assert_eq!(done, changed_status.new_status, "New status does not match");
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
} }
} }
Err(err) => {
let _ = db_mgr::drop_database(&tm_pool, &db_name).await; assert!(false, "Error: {:?}", err);
}
} }
let _ = db_mgr::drop_database(&tm_pool, &db_name).await;
} }
#[tokio::test] #[tokio::test]
@@ -1038,7 +1120,7 @@ mod tests {
// Send request // Send request
match sequence_flow::queue_song_flow(&app).await { match sequence_flow::queue_song_flow(&app).await {
Ok(response) => { Ok((response, _user_id)) => {
let resp = get_resp_data::< let resp = get_resp_data::<
crate::callers::metadata::response::fetch_metadata::Response, crate::callers::metadata::response::fetch_metadata::Response,
>(response) >(response)
@@ -1354,7 +1436,7 @@ mod tests {
// Send request // Send request
match sequence_flow::queue_song_flow(&app).await { match sequence_flow::queue_song_flow(&app).await {
Ok(response) => { Ok((response, user_id)) => {
let resp = get_resp_data::< let resp = get_resp_data::<
crate::callers::metadata::response::fetch_metadata::Response, crate::callers::metadata::response::fetch_metadata::Response,
>(response) >(response)
@@ -1362,7 +1444,7 @@ mod tests {
assert_eq!(false, resp.data.is_empty(), "Data should not be empty"); assert_eq!(false, resp.data.is_empty(), "Data should not be empty");
let song_q_id = resp.data[0].song_queue_id; let song_q_id = resp.data[0].song_queue_id;
match create_song_req(&app, &song_q_id).await { match create_song_req(&app, &song_q_id, &user_id).await {
Ok(response) => { Ok(response) => {
let resp = get_resp_data::< let resp = get_resp_data::<
crate::callers::song::response::create_metadata::Response, crate::callers::song::response::create_metadata::Response,
@@ -1417,7 +1499,7 @@ mod tests {
// Send request // Send request
match sequence_flow::queue_song_flow(&app).await { match sequence_flow::queue_song_flow(&app).await {
Ok(response) => { Ok((response, user_id)) => {
let resp = get_resp_data::< let resp = get_resp_data::<
crate::callers::metadata::response::fetch_metadata::Response, crate::callers::metadata::response::fetch_metadata::Response,
>(response) >(response)
@@ -1425,7 +1507,7 @@ mod tests {
assert_eq!(false, resp.data.is_empty(), "Data should not be empty"); assert_eq!(false, resp.data.is_empty(), "Data should not be empty");
let song_queue_id = resp.data[0].song_queue_id; let song_queue_id = resp.data[0].song_queue_id;
match create_song_req(&app, &song_queue_id).await { match create_song_req(&app, &song_queue_id, &user_id).await {
Ok(response) => { Ok(response) => {
let resp = get_resp_data::< let resp = get_resp_data::<
crate::callers::song::response::create_metadata::Response, crate::callers::song::response::create_metadata::Response,
@@ -1512,7 +1594,7 @@ mod tests {
// Send request // Send request
match sequence_flow::queue_song_flow(&app).await { match sequence_flow::queue_song_flow(&app).await {
Ok(response) => { Ok((response, user_id)) => {
let resp = get_resp_data::< let resp = get_resp_data::<
crate::callers::metadata::response::fetch_metadata::Response, crate::callers::metadata::response::fetch_metadata::Response,
>(response) >(response)
@@ -1520,7 +1602,7 @@ mod tests {
assert_eq!(false, resp.data.is_empty(), "Data should not be empty"); assert_eq!(false, resp.data.is_empty(), "Data should not be empty");
let song_q_id = resp.data[0].song_queue_id; let song_q_id = resp.data[0].song_queue_id;
match create_song_req(&app, &song_q_id).await { match create_song_req(&app, &song_q_id, &user_id).await {
Ok(response) => { Ok(response) => {
let resp = get_resp_data::< let resp = get_resp_data::<
crate::callers::song::response::create_metadata::Response, crate::callers::song::response::create_metadata::Response,
@@ -1617,7 +1699,7 @@ mod tests {
let app = init::app(pool).await; let app = init::app(pool).await;
match sequence_flow::queue_song_flow(&app).await { match sequence_flow::queue_song_flow(&app).await {
Ok(response) => { Ok((response, user_id)) => {
let resp = get_resp_data::< let resp = get_resp_data::<
crate::callers::metadata::response::fetch_metadata::Response, crate::callers::metadata::response::fetch_metadata::Response,
>(response) >(response)
@@ -1625,7 +1707,7 @@ mod tests {
assert_eq!(false, resp.data.is_empty(), "Data should not be empty"); assert_eq!(false, resp.data.is_empty(), "Data should not be empty");
let song_queue_id = resp.data[0].song_queue_id; let song_queue_id = resp.data[0].song_queue_id;
match create_song_req(&app, &song_queue_id).await { match create_song_req(&app, &song_queue_id, &user_id).await {
Ok(response) => { Ok(response) => {
let resp = get_resp_data::< let resp = get_resp_data::<
crate::callers::song::response::create_metadata::Response, crate::callers::song::response::create_metadata::Response,