Update song queue flow #142

Merged
kdeng00 merged 9 commits from song_queue_status_update into v0.2 2025-06-07 14:47:43 -04:00
3 changed files with 360 additions and 303 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE IF NOT EXISTS "songQueue" ( 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', 'processing', 'done')), status TEXT CHECK (status IN ('pending', 'ready', 'processing', 'done')),
data BYTEA NULL data BYTEA NULL
); );
+3 -2
View File
@@ -148,11 +148,12 @@ pub mod response {
pub mod status { pub mod status {
pub const PENDING: &str = "pending"; pub const PENDING: &str = "pending";
pub const READY: &str = "ready";
pub const PROCESSING: &str = "processing"; pub const PROCESSING: &str = "processing";
pub const DONE: &str = "done"; pub const DONE: &str = "done";
pub async fn is_valid(status: &str) -> bool { pub async fn is_valid(status: &str) -> bool {
status == PENDING || status == PROCESSING || status == DONE status == PENDING || status == PROCESSING || status == DONE || status == READY
} }
} }
@@ -396,7 +397,7 @@ mod song_queue {
"#, "#,
) )
.bind(super::status::PROCESSING) .bind(super::status::PROCESSING)
.bind(super::status::PENDING) .bind(super::status::READY)
.fetch_one(pool) .fetch_one(pool)
.await .await
.map_err(|e| { .map_err(|e| {
+282 -226
View File
@@ -153,7 +153,6 @@ pub async fn root() -> &'static str {
mod tests { mod tests {
use crate::db; use crate::db;
use std::io::Write;
use std::usize; use std::usize;
use common_multipart_rfc7578::client::multipart::{ use common_multipart_rfc7578::client::multipart::{
@@ -408,6 +407,25 @@ mod tests {
app.clone().oneshot(req).await app.clone().oneshot(req).await
} }
async fn update_song_queue_status_req(
app: &axum::Router,
song_queue_id: &uuid::Uuid,
) -> Result<axum::response::Response, std::convert::Infallible> {
let payload = serde_json::json!({
"id": &song_queue_id,
"status": crate::callers::song::status::READY
});
let req = axum::http::Request::builder()
.method(axum::http::Method::PATCH)
.uri(crate::callers::endpoints::QUEUESONG)
.header(axum::http::header::CONTENT_TYPE, "application/json")
.body(axum::body::Body::from(payload.to_string()))
.unwrap();
app.clone().oneshot(req).await
}
async fn get_queued_coverart( async fn get_queued_coverart(
app: &axum::Router, app: &axum::Router,
coverart_queue_id: &uuid::Uuid, coverart_queue_id: &uuid::Uuid,
@@ -508,6 +526,63 @@ mod tests {
} }
} }
} }
// Returns coverart response and song_queue_id
pub async fn queue_song_and_coverart_flow(
app: &axum::Router,
) -> Result<(axum::response::Response, uuid::Uuid), std::convert::Infallible> {
match queue_song_flow(&app).await {
Ok(song_response) => {
let resp = super::get_resp_data::<
crate::callers::metadata::response::fetch_metadata::Response,
>(song_response)
.await;
assert_eq!(false, resp.data.is_empty(), "Data should not be empty");
let song_queue_id = resp.data[0].song_queue_id;
match super::create_song_req(&app, &song_queue_id).await {
Ok(response) => {
let resp = super::get_resp_data::<
crate::callers::song::response::create_metadata::Response,
>(response)
.await;
assert_eq!(
false,
resp.data.is_empty(),
"No songs found, Response {:?}",
resp
);
let song = &resp.data[0];
let song_id = song.id;
assert_eq!(
false,
song_id.is_nil(),
"Song id should not be nil {:?}",
song
);
eprintln!("Song: {:?}", song);
match queue_coverart_flow(&app, &song_queue_id).await {
Ok(response) => Ok((response, song_queue_id)),
Err(err) => {
assert!(false, "Error: {:?}", err);
Err(err)
}
}
}
Err(err) => {
assert!(false, "Error: {:?}", err);
Err(err)
}
}
}
Err(err) => {
assert!(false, "Error: {:?}", err);
Err(err)
}
}
}
} }
pub async fn resp_to_bytes( pub async fn resp_to_bytes(
@@ -577,6 +652,12 @@ 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 {
use super::*;
use std::io::Write;
#[tokio::test] #[tokio::test]
async fn test_song_fetch_queue_item() { async fn test_song_fetch_queue_item() {
let tm_pool = db_mgr::get_pool().await.unwrap(); let tm_pool = db_mgr::get_pool().await.unwrap();
@@ -596,6 +677,82 @@ mod tests {
let app = init::app(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 target_status = 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!(
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 // Send request
match song_queue_req(&app).await { match song_queue_req(&app).await {
Ok(response) => { Ok(response) => {
@@ -604,13 +761,64 @@ mod tests {
assert_eq!(false, resp.data.is_empty(), "Should not be empty"); assert_eq!(false, resp.data.is_empty(), "Should not be empty");
assert_eq!(false, resp.data[0].is_nil(), "Should not be empty"); assert_eq!(false, resp.data[0].is_nil(), "Should not be empty");
match fetch_queue_req(&app).await { 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) => { 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"
);
} }
Err(err) => { Err(err) => {
assert!(false, "Error: {:?}", err); assert!(false, "Error: {:?}", err);
@@ -620,6 +828,15 @@ 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);
}
}; };
let _ = db_mgr::drop_database(&tm_pool, &db_name).await; let _ = db_mgr::drop_database(&tm_pool, &db_name).await;
@@ -651,15 +868,7 @@ mod tests {
get_resp_data::<crate::callers::song::response::Response>(response).await; 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.is_empty(), "Should not be empty");
assert_eq!(false, resp.data[0].is_nil(), "Should not be empty"); assert_eq!(false, resp.data[0].is_nil(), "Should not be empty");
let id = resp.data[0];
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");
let id = resp.data[0].id;
match fetch_queue_data_req(&app, &id).await { match fetch_queue_data_req(&app, &id).await {
Ok(response) => match resp_to_bytes(response).await { Ok(response) => match resp_to_bytes(response).await {
@@ -682,15 +891,74 @@ mod tests {
Err(err) => { Err(err) => {
assert!(false, "Error: {:?}", 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) => { Err(err) => {
assert!(false, "Error: {:?}", 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] #[tokio::test]
async fn test_song_metadata_queue() { async fn test_song_metadata_queue() {
@@ -885,95 +1153,6 @@ mod tests {
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_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;
// 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");
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");
let old = &resp.data[0].status;
let done = crate::callers::song::status::DONE;
let payload = serde_json::json!({
"id": &resp.data[0].id,
"status": done,
});
match app
.clone()
.oneshot(
axum::http::Request::builder()
.method(axum::http::Method::PATCH)
.uri(crate::callers::endpoints::QUEUESONG)
.header(axum::http::header::CONTENT_TYPE, "application/json")
.body(axum::body::Body::from(payload.to_string()))
.unwrap(),
)
.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) => {
assert!(false, "Error: {:?}", err);
}
}
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
};
let _ = db_mgr::drop_database(&tm_pool, &db_name).await;
}
#[tokio::test] #[tokio::test]
async fn test_fetch_coverart_queue_without_data() { async fn test_fetch_coverart_queue_without_data() {
let tm_pool = db_mgr::get_pool().await.unwrap(); let tm_pool = db_mgr::get_pool().await.unwrap();
@@ -1130,129 +1309,6 @@ mod tests {
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_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");
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");
let id = resp.data[0].id;
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"
);
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
}
}
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] #[tokio::test]
async fn test_create_song() { async fn test_create_song() {
let tm_pool = db_mgr::get_pool().await.unwrap(); let tm_pool = db_mgr::get_pool().await.unwrap();