Update song queue flow (#142)

* Added new status and made changes to song queue status code

* Saving changes

* Got a test working

* Got another test working

* Got the last test working

* Code cleanup

* More minor changes

* More minor changes

* Minor changes
This commit was merged in pull request #142.
This commit is contained in:
KD
2025-06-07 14:47:42 -04:00
committed by GitHub
parent eb1f19a96e
commit 6f2e8d6e17
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| {
+356 -300
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,119 +652,312 @@ mod tests {
let _ = db_mgr::drop_database(&tm_pool, &db_name).await; let _ = db_mgr::drop_database(&tm_pool, &db_name).await;
} }
#[tokio::test] mod special_area {
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 { use super::*;
Ok(_) => {
println!("Success");
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
}
let pool = db_mgr::connect_to_db(&db_name).await.unwrap(); use std::io::Write;
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;
// Send request 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");
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);
}
};
let _ = db_mgr::drop_database(&tm_pool, &db_name).await; 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_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 { match sequence_flow::queue_song_and_coverart_flow(&app).await {
Ok(_) => { Ok((resp_one, song_queue_id)) => {
println!("Success"); let resp = get_resp_data::<
} crate::callers::coverart::response::fetch_coverart_no_data::Response,
Err(err) => { >(resp_one)
assert!(false, "Error: {:?}", err); .await;
} assert_eq!(false, resp.data.is_empty(), "Should not be empty");
}
let pool = db_mgr::connect_to_db(&db_name).await.unwrap(); let _resp_coverart_queue_id = resp.data[0].id;
db::migrations(&pool).await;
let app = init::app(pool).await; let old = crate::callers::song::status::PENDING;
let target_status = crate::callers::song::status::READY;
// Send request match update_song_queue_status_req(&app, &song_queue_id).await {
match song_queue_req(&app).await { Ok(response) => {
Ok(response) => { let resp = get_resp_data::<
let resp = crate::callers::song::response::update_status::Response,
get_resp_data::<crate::callers::song::response::Response>(response).await; >(response)
assert_eq!(false, resp.data.is_empty(), "Should not be empty"); .await;
assert_eq!(false, resp.data[0].is_nil(), "Should not be empty"); assert_eq!(false, resp.data.is_empty(), "Should not be empty");
let changed_status = &resp.data[0];
match fetch_queue_req(&app).await { assert_eq!(
Ok(response) => { *old, changed_status.old_status,
let resp = get_resp_data::< "Old status does not match"
crate::callers::song::response::fetch_queue_song::Response, );
>(response) assert_eq!(
.await; target_status, changed_status.new_status,
assert_eq!(false, resp.data.is_empty(), "Should not be empty"); "New status does not match"
let id = resp.data[0].id; );
match fetch_queue_data_req(&app, &id).await { match fetch_queue_req(&app).await {
Ok(response) => match resp_to_bytes(response).await { Ok(response) => {
Ok(bytes) => { let resp = get_resp_data::<
assert_eq!( crate::callers::song::response::fetch_queue_song::Response,
false, >(response)
bytes.is_empty(), .await;
"Queued data 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);
} }
}, }
}
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 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) => { 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_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);
} }
} }
Err(err) => {
assert!(false, "Error: {:?}", err);
}
};
let _ = db_mgr::drop_database(&tm_pool, &db_name).await; 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) => {
assert!(false, "Error: {:?}", err);
}
}
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
}
let _ = db_mgr::drop_database(&tm_pool, &db_name).await;
}
} }
#[tokio::test] #[tokio::test]
@@ -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();