use crate::testing_prelude::*;
use std::fs::metadata as fs_metadata;
#[tokio::test]
async fn verify_command_mocked() -> Result<(), TestError> {
init_logger();
let test_dir = TestDirectory::new();
let album = AlbumProvider::get(SampleFormat::default()).await;
let host = HostBuilder::new()
.with_mock_api(album)
.with_test_options(&test_dir)
.await
.expect_build();
let provider = host.services.get_required::<SourceProvider>();
let verifier = host.services.get_required::<VerifyCommand>();
let source = provider
.get(AlbumConfig::TORRENT_ID)
.await
.expect("should not fail")
.expect("should find source");
let result = verifier.execute(&source).await.expect("should not fail");
if !result.verified() {
for issue in &result.issues {
eprintln!("Issue: {issue}");
}
}
assert!(result.verified());
Ok(())
}
#[tokio::test]
async fn get_source_torrent_repeat_call() -> Result<(), TestError> {
init_logger();
let test_dir = TestDirectory::new();
let album = AlbumProvider::get(SampleFormat::default()).await;
let host = HostBuilder::new()
.with_mock_api(album)
.with_test_options(&test_dir)
.await
.expect_build();
let provider = host.services.get_required::<SourceProvider>();
let verifier = host.services.get_required::<VerifyCommand>();
let source = provider
.get(AlbumConfig::TORRENT_ID)
.await
.expect("should not fail")
.expect("should find source");
let paths = host.services.get_required::<PathManager>();
let torrent_path = paths.get_source_torrent_path(source.torrent.id);
assert!(!torrent_path.is_file(), "torrent should not be cached yet");
let result = verifier.get_source_torrent(&source).await?;
assert_eq!(result, torrent_path);
assert!(
torrent_path.is_file(),
"torrent should be cached after download"
);
let metadata = fs_metadata(&torrent_path)?;
let first_modified = metadata.modified()?;
let result = verifier.get_source_torrent(&source).await?;
assert_eq!(result, torrent_path);
let metadata = fs_metadata(&torrent_path)?;
let second_modified = metadata.modified()?;
assert_eq!(
first_modified, second_modified,
"cached file should not be rewritten"
);
Ok(())
}
#[tokio::test]
async fn get_source_torrent_download_failure() -> Result<(), TestError> {
init_logger();
let test_dir = TestDirectory::new();
let album = AlbumProvider::get(SampleFormat::default()).await;
let failing_api = album.api().with_download_torrent(Err(GazelleError {
operation: GazelleOperation::SendRequest,
source: ErrorSource::Io(IoError::new(
ErrorKind::ConnectionRefused,
"simulated download failure",
)),
}));
let host = HostBuilder::new()
.with_mock_client(failing_api)
.with_test_options(&test_dir)
.await
.expect_build();
let provider = host.services.get_required::<SourceProvider>();
let verifier = host.services.get_required::<VerifyCommand>();
let source = provider
.get(AlbumConfig::TORRENT_ID)
.await
.expect("should not fail")
.expect("should find source");
let paths = host.services.get_required::<PathManager>();
let torrent_path = paths.get_source_torrent_path(source.torrent.id);
let result = verifier.get_source_torrent(&source).await;
assert!(result.is_err(), "download should have failed");
assert!(
!torrent_path.is_file(),
"no file should remain at final torrent path after failed download, but found: {}",
torrent_path.display()
);
Ok(())
}
#[tokio::test]
async fn hash_check_mismatch() -> Result<(), TestError> {
init_logger();
let test_dir = TestDirectory::new();
let album = AlbumProvider::get(SampleFormat::default()).await;
let host = HostBuilder::new()
.with_mock_api(album)
.with_test_options(&test_dir)
.await
.expect_build();
let provider = host.services.get_required::<SourceProvider>();
let verifier = host.services.get_required::<VerifyCommand>();
let mut source = provider
.get(AlbumConfig::TORRENT_ID)
.await
.expect("should not fail")
.expect("should find source");
verifier
.get_source_torrent(&source)
.await
.expect("should download");
let empty_dir = test_dir.join("empty");
create_dir(&empty_dir)?;
source.directory = empty_dir;
let result = verifier.hash_check(&source).await;
let issue = result
.expect("hash check should not return infrastructure failure")
.expect("hash check should return an issue");
assert!(
matches!(issue, SourceIssue::MissingFile { .. }),
"expected MissingFile, got: {issue}"
);
Ok(())
}
#[test]
#[allow(clippy::indexing_slicing)]
fn verify_command_subdirectory_checks() {
let source_dir = PathBuf::from("source/dir");
let result = VerifyCommand::subdirectory_checks(&[
FlacFile::new(PathBuf::from("source/dir/a.flac"), &source_dir),
FlacFile::new(PathBuf::from("source/dir/b.flac"), &source_dir),
]);
assert_eq!(result.len(), 0);
let result = VerifyCommand::subdirectory_checks(&[
FlacFile::new(PathBuf::from("source/dir/a/b.flac"), &source_dir),
FlacFile::new(PathBuf::from("source/dir/a/c.flac"), &source_dir),
]);
assert_eq!(result.len(), 1);
let result = VerifyCommand::subdirectory_checks(&[
FlacFile::new(PathBuf::from("source/dir/CD1/a.flac"), &source_dir),
FlacFile::new(PathBuf::from("source/dir/CD2/b.flac"), &source_dir),
]);
assert_eq!(result.len(), 0);
let result = VerifyCommand::subdirectory_checks(&[FlacFile::new(
PathBuf::from("source/dir/c/d.flac"),
&source_dir,
)]);
assert_eq!(result.len(), 1);
assert_eq!(
result[0].to_string(),
SourceIssue::UnnecessaryDirectory {
prefix: PathBuf::from("c")
}
.to_string()
);
let result = VerifyCommand::subdirectory_checks(&[FlacFile::new(
PathBuf::from("/root/album/track.flac"),
&PathBuf::from("/root/album/"),
)]);
assert_eq!(result.len(), 0);
}
#[test]
fn check_directory_exists_exists() {
let mut source = mock_source();
source.directory = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
assert!(check_directory_exists(&source).is_none());
}
#[test]
fn check_directory_exists_missing() {
let mut source = mock_source();
source.directory = PathBuf::from("/nonexistent/path");
assert_eq!(
check_directory_exists(&source),
Some(SourceIssue::MissingDirectory {
path: PathBuf::from("/nonexistent/path")
})
);
}
#[test]
fn check_flac_count_matches() {
let source = mock_source();
let actual = source.torrent.get_flacs().len();
assert!(check_flac_count(&source, actual).is_none());
}
#[test]
fn check_flac_count_mismatch() {
let source = mock_source();
assert_eq!(
check_flac_count(&source, 5),
Some(SourceIssue::FlacCount {
expected: 1,
actual: 5
})
);
}
#[test]
fn check_path_length_within_limit() {
let path = PathBuf::from("a".repeat(180));
assert_eq!(check_path_length(&path), None);
}
#[test]
fn check_path_length_exceeds_limit() {
let path = PathBuf::from("a".repeat(185));
assert_eq!(
check_path_length(&path),
Some(SourceIssue::Length {
path: path.clone(),
excess: 5,
})
);
}
#[tokio::test]
async fn verify_command_execute_no_reports_set() -> Result<(), TestError> {
init_logger();
let test_dir = TestDirectory::new();
let reports_dir = test_dir.reports();
let content_dir = test_dir.join("tagless_source");
create_dir(&content_dir)?;
FlacGenerator::new()
.with_filename("01 - track.flac")
.omit_vorbis_comments()
.generate(&content_dir)
.await
.expect("generate tagless flac");
let album = AlbumProvider::get(SampleFormat::default()).await;
let mut builder = HostBuilder::new();
let _ = builder
.with_mock_api(album)
.with_test_options(&test_dir)
.await
.with_options(VerifyOptions {
no_hash_check: true,
exclude_tags: None,
})
.with_options(ReportOptions {
reports_dir: reports_dir.clone(),
no_reports: true,
});
let host = builder.expect_build();
let provider = host.services.get_required::<SourceProvider>();
let verifier = host.services.get_required::<VerifyCommand>();
let mut source = provider
.get(AlbumConfig::TORRENT_ID)
.await
.expect("should not fail")
.expect("should find source");
source.directory = content_dir;
let _ = verifier
.execute(&source)
.await
.expect("verify should succeed");
assert!(!reports_dir.exists(), "expected no reports directory");
Ok(())
}
fn mock_source() -> Source {
Source {
torrent: Torrent::mock(),
group: Group::mock(),
targets: TargetFormat::all(),
format: SourceFormat::Flac,
directory: PathBuf::from("/tmp/test"),
metadata: Metadata::new(&Group::mock(), &Torrent::mock()),
url: get_permalink(RED_URL, 123, 456),
}
}