Initial commit
This commit is contained in:
239
tests/integration.rs
Normal file
239
tests/integration.rs
Normal file
@@ -0,0 +1,239 @@
|
||||
use shanty_db::entities::download_queue::DownloadStatus;
|
||||
use shanty_db::{Database, queries};
|
||||
use shanty_dl::backend::{AudioFormat, BackendConfig, DownloadBackend, DownloadResult, DownloadTarget, SearchResult};
|
||||
use shanty_dl::error::DlResult;
|
||||
use shanty_dl::queue::{run_queue, download_single};
|
||||
use tempfile::TempDir;
|
||||
|
||||
/// Mock backend for testing without yt-dlp.
|
||||
struct MockBackend {
|
||||
/// If true, downloads will fail.
|
||||
should_fail: bool,
|
||||
}
|
||||
|
||||
impl MockBackend {
|
||||
fn new(should_fail: bool) -> Self {
|
||||
Self { should_fail }
|
||||
}
|
||||
}
|
||||
|
||||
impl DownloadBackend for MockBackend {
|
||||
async fn check_available(&self) -> DlResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn search(&self, query: &str) -> DlResult<Vec<SearchResult>> {
|
||||
Ok(vec![SearchResult {
|
||||
url: format!("https://example.com/{}", query.replace(' ', "_")),
|
||||
title: query.to_string(),
|
||||
artist: Some("Test Artist".to_string()),
|
||||
duration: Some(180.0),
|
||||
source: "mock".to_string(),
|
||||
}])
|
||||
}
|
||||
|
||||
async fn download(
|
||||
&self,
|
||||
target: &DownloadTarget,
|
||||
config: &BackendConfig,
|
||||
) -> DlResult<DownloadResult> {
|
||||
if self.should_fail {
|
||||
return Err(shanty_dl::DlError::DownloadFailed("mock failure".into()));
|
||||
}
|
||||
|
||||
let title = match target {
|
||||
DownloadTarget::Url(u) => u.clone(),
|
||||
DownloadTarget::Query(q) => q.clone(),
|
||||
};
|
||||
|
||||
let file_name = format!("{}.{}", title.replace(' ', "_"), config.format);
|
||||
let file_path = config.output_dir.join(&file_name);
|
||||
std::fs::write(&file_path, b"fake audio data").unwrap();
|
||||
|
||||
Ok(DownloadResult {
|
||||
file_path,
|
||||
title,
|
||||
artist: Some("Test Artist".into()),
|
||||
duration: Some(180.0),
|
||||
source_url: "https://example.com/test".into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_db() -> Database {
|
||||
Database::new("sqlite::memory:")
|
||||
.await
|
||||
.expect("failed to create test database")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_queue_process_success() {
|
||||
let db = test_db().await;
|
||||
let dir = TempDir::new().unwrap();
|
||||
let backend = MockBackend::new(false);
|
||||
|
||||
let config = BackendConfig {
|
||||
output_dir: dir.path().to_owned(),
|
||||
format: AudioFormat::Opus,
|
||||
cookies_path: None,
|
||||
};
|
||||
|
||||
// Enqueue an item
|
||||
queries::downloads::enqueue(db.conn(), "Test Song", None, "mock")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Process queue
|
||||
let stats = run_queue(db.conn(), &backend, &config, false).await.unwrap();
|
||||
assert_eq!(stats.downloads_attempted, 1);
|
||||
assert_eq!(stats.downloads_completed, 1);
|
||||
assert_eq!(stats.downloads_failed, 0);
|
||||
|
||||
// Verify status in DB
|
||||
let items = queries::downloads::list(db.conn(), Some(DownloadStatus::Completed))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(items.len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_queue_process_failure() {
|
||||
let db = test_db().await;
|
||||
let dir = TempDir::new().unwrap();
|
||||
let backend = MockBackend::new(true);
|
||||
|
||||
let config = BackendConfig {
|
||||
output_dir: dir.path().to_owned(),
|
||||
format: AudioFormat::Opus,
|
||||
cookies_path: None,
|
||||
};
|
||||
|
||||
queries::downloads::enqueue(db.conn(), "Failing Song", None, "mock")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let stats = run_queue(db.conn(), &backend, &config, false).await.unwrap();
|
||||
assert_eq!(stats.downloads_attempted, 1);
|
||||
assert_eq!(stats.downloads_failed, 1);
|
||||
|
||||
// Check it's marked as failed
|
||||
let items = queries::downloads::list(db.conn(), Some(DownloadStatus::Failed))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(items.len(), 1);
|
||||
assert!(items[0].error_message.is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_queue_dry_run() {
|
||||
let db = test_db().await;
|
||||
let dir = TempDir::new().unwrap();
|
||||
let backend = MockBackend::new(false);
|
||||
|
||||
let config = BackendConfig {
|
||||
output_dir: dir.path().to_owned(),
|
||||
format: AudioFormat::Opus,
|
||||
cookies_path: None,
|
||||
};
|
||||
|
||||
queries::downloads::enqueue(db.conn(), "Dry Run Song", None, "mock")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let stats = run_queue(db.conn(), &backend, &config, true).await.unwrap();
|
||||
assert_eq!(stats.downloads_attempted, 1);
|
||||
assert_eq!(stats.downloads_skipped, 1);
|
||||
assert_eq!(stats.downloads_completed, 0);
|
||||
|
||||
// No files should exist
|
||||
let entries: Vec<_> = std::fs::read_dir(dir.path()).unwrap().collect();
|
||||
assert!(entries.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_download_single_success() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let backend = MockBackend::new(false);
|
||||
|
||||
let config = BackendConfig {
|
||||
output_dir: dir.path().to_owned(),
|
||||
format: AudioFormat::Mp3,
|
||||
cookies_path: None,
|
||||
};
|
||||
|
||||
download_single(
|
||||
&backend,
|
||||
DownloadTarget::Query("Test Song".into()),
|
||||
&config,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// File should exist
|
||||
let entries: Vec<_> = std::fs::read_dir(dir.path())
|
||||
.unwrap()
|
||||
.filter_map(|e| e.ok())
|
||||
.collect();
|
||||
assert_eq!(entries.len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_queue_retry() {
|
||||
let db = test_db().await;
|
||||
|
||||
// Enqueue and manually fail an item
|
||||
let item = queries::downloads::enqueue(db.conn(), "Retry Song", None, "mock")
|
||||
.await
|
||||
.unwrap();
|
||||
queries::downloads::update_status(db.conn(), item.id, DownloadStatus::Failed, Some("oops"))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Retry it
|
||||
queries::downloads::retry_failed(db.conn(), item.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Should be pending again with retry_count = 1
|
||||
let pending = queries::downloads::list(db.conn(), Some(DownloadStatus::Pending))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(pending.len(), 1);
|
||||
assert_eq!(pending[0].retry_count, 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_wanted_item_status_updated_on_download() {
|
||||
use shanty_db::entities::wanted_item::{ItemType, WantedStatus};
|
||||
|
||||
let db = test_db().await;
|
||||
let dir = TempDir::new().unwrap();
|
||||
let backend = MockBackend::new(false);
|
||||
|
||||
let config = BackendConfig {
|
||||
output_dir: dir.path().to_owned(),
|
||||
format: AudioFormat::Opus,
|
||||
cookies_path: None,
|
||||
};
|
||||
|
||||
// Create a wanted item
|
||||
let wanted = queries::wanted::add(db.conn(), ItemType::Track, None, None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(wanted.status, WantedStatus::Wanted);
|
||||
|
||||
// Enqueue download linked to the wanted item
|
||||
queries::downloads::enqueue(db.conn(), "Wanted Song", Some(wanted.id), "mock")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Process queue
|
||||
run_queue(db.conn(), &backend, &config, false).await.unwrap();
|
||||
|
||||
// Wanted item should now be Downloaded
|
||||
let updated = queries::wanted::get_by_id(db.conn(), wanted.id)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(updated.status, WantedStatus::Downloaded);
|
||||
}
|
||||
Reference in New Issue
Block a user