use super::*;
#[test]
fn not_in_manifest_downloads() {
let manifest = Manifest::new();
let d = vec![desired("a", "a.flac", AudioFormat::Flac, "m", "art")];
let plan = reconcile(&manifest, &d, &HashMap::new(), &mirror_ok());
assert_eq!(
plan.actions,
vec![Action::Download {
clip: clip("a"),
lineage: lineage("a"),
path: "a.flac".to_string(),
format: AudioFormat::Flac,
}]
);
}
#[test]
fn unchanged_clip_skips() {
let mut manifest = Manifest::new();
manifest.insert("a", entry("a.flac", AudioFormat::Flac, "m", "art"));
let d = vec![desired("a", "a.flac", AudioFormat::Flac, "m", "art")];
let plan = reconcile(&manifest, &d, &local_present("a"), &mirror_ok());
assert_eq!(
plan.actions,
vec![Action::Skip {
clip_id: "a".to_string()
}]
);
}
#[test]
fn nested_manifest_path_reconciles_without_rename_or_delete() {
let clip = Clip {
id: "clipaaaa-1234".to_owned(),
title: "Song".to_owned(),
display_name: "alice".to_owned(),
image_large_url: "https://art.suno.ai/clipaaaa-1234/large.jpg".to_owned(),
..Clip::default()
};
let clips = [&clip];
let modes: HashMap<String, Vec<SourceMode>> = [(clip.id.clone(), vec![SourceMode::Mirror])]
.into_iter()
.collect();
let desired = crate::desired::build_desired(
&clips,
AudioFormat::Flac,
&modes,
&HashMap::new(),
&BTreeSet::new(),
crate::desired::ArtifactToggles::default(),
&crate::naming::NamingConfig::default(),
);
let d = &desired[0];
let stored_audio = d.path.replace('\\', "/");
assert!(
!stored_audio.contains('\\') && stored_audio.contains('/'),
"expected a nested forward-slash path, got {stored_audio}"
);
let cover = d
.artifacts
.iter()
.find(|a| a.kind == ArtifactKind::CoverJpg)
.expect("an art-bearing clip yields a cover.jpg");
let mut manifest = Manifest::new();
manifest.insert(
clip.id.clone(),
ManifestEntry {
path: stored_audio.clone(),
format: AudioFormat::Flac,
meta_hash: d.meta_hash.clone(),
art_hash: d.art_hash.clone(),
size: 100,
cover_jpg: Some(ArtifactState {
path: cover.path.replace('\\', "/"),
hash: cover.hash.clone(),
}),
..Default::default()
},
);
let local: HashMap<String, LocalFile> = [(clip.id.clone(), present(100))].into_iter().collect();
let plan = reconcile(&manifest, &desired, &local, &mirror_ok());
assert_eq!(
plan.renames(),
0,
"the recomputed path drifted from the stored forward-slash path"
);
assert_eq!(plan.deletes(), 0, "no clip should be deleted");
assert_eq!(plan.reformats(), 0);
assert_eq!(plan.downloads(), 0);
assert_eq!(plan.retags(), 0);
assert_eq!(plan.artifact_writes(), 0, "the cover.jpg drifted");
assert_eq!(plan.artifact_moves(), 0);
assert_eq!(plan.skips(), 1, "the unchanged clip is skipped");
}
#[test]
fn meta_change_retags_in_place() {
let mut manifest = Manifest::new();
manifest.insert("a", entry("a.flac", AudioFormat::Flac, "old", "art"));
let d = vec![desired("a", "a.flac", AudioFormat::Flac, "new", "art")];
let plan = reconcile(&manifest, &d, &local_present("a"), &mirror_ok());
assert_eq!(
plan.actions,
vec![Action::Retag {
clip: clip("a"),
lineage: lineage("a"),
path: "a.flac".to_string(),
}]
);
}
#[test]
fn art_change_retags_in_place() {
let mut manifest = Manifest::new();
manifest.insert("a", entry("a.flac", AudioFormat::Flac, "m", "old-art"));
let d = vec![desired("a", "a.flac", AudioFormat::Flac, "m", "new-art")];
let plan = reconcile(&manifest, &d, &local_present("a"), &mirror_ok());
assert_eq!(
plan.actions,
vec![Action::Retag {
clip: clip("a"),
lineage: lineage("a"),
path: "a.flac".to_string(),
}]
);
}
#[test]
fn rename_when_path_changes() {
let mut manifest = Manifest::new();
manifest.insert("a", entry("old/a.flac", AudioFormat::Flac, "m", "art"));
let d = vec![desired("a", "new/a.flac", AudioFormat::Flac, "m", "art")];
let plan = reconcile(&manifest, &d, &local_present("a"), &mirror_ok());
assert_eq!(
plan.actions,
vec![Action::Rename {
from: "old/a.flac".to_string(),
to: "new/a.flac".to_string(),
}]
);
}
#[test]
fn case_only_path_change_is_not_a_rename() {
let mut manifest = Manifest::new();
manifest.insert(
"a",
entry("Creator/Song.flac", AudioFormat::Flac, "m", "art"),
);
let d = vec![desired(
"a",
"Creator/song.flac",
AudioFormat::Flac,
"m",
"art",
)];
let plan = reconcile(&manifest, &d, &local_present("a"), &mirror_ok());
assert_eq!(
plan.actions,
vec![Action::Skip {
clip_id: "a".to_string()
}]
);
}
#[test]
fn case_only_path_change_with_meta_drift_retags_in_place() {
let mut manifest = Manifest::new();
manifest.insert(
"a",
entry("Creator/Song.flac", AudioFormat::Flac, "old", "art"),
);
let d = vec![desired(
"a",
"Creator/song.flac",
AudioFormat::Flac,
"new",
"art",
)];
let plan = reconcile(&manifest, &d, &local_present("a"), &mirror_ok());
assert_eq!(
plan.actions,
vec![Action::Retag {
clip: clip("a"),
lineage: lineage("a"),
path: "Creator/Song.flac".to_string(),
}]
);
}
#[test]
fn rename_with_meta_change_also_retags() {
let mut manifest = Manifest::new();
manifest.insert("a", entry("old/a.flac", AudioFormat::Flac, "old", "art"));
let d = vec![desired("a", "new/a.flac", AudioFormat::Flac, "new", "art")];
let plan = reconcile(&manifest, &d, &local_present("a"), &mirror_ok());
assert_eq!(
plan.actions,
vec![
Action::Rename {
from: "old/a.flac".to_string(),
to: "new/a.flac".to_string(),
},
Action::Retag {
clip: clip("a"),
lineage: lineage("a"),
path: "new/a.flac".to_string(),
},
]
);
}
#[test]
fn bulk_album_rename_moves_and_retags_without_redownload() {
let mut manifest = Manifest::new();
for id in ["a", "b", "c"] {
manifest.insert(
id,
entry(
&format!("Creator/Old Album/{id}.flac"),
AudioFormat::Flac,
"old-meta",
"art",
),
);
}
let d: Vec<Desired> = ["a", "b", "c"]
.iter()
.map(|id| {
desired(
id,
&format!("Creator/New Album/{id}.flac"),
AudioFormat::Flac,
"new-meta",
"art",
)
})
.collect();
let local: HashMap<String, LocalFile> = ["a", "b", "c"]
.iter()
.map(|id| (id.to_string(), present(100)))
.collect();
let plan = reconcile(&manifest, &d, &local, &mirror_ok());
assert_eq!(plan.renames(), 3, "every member folder move is a rename");
assert_eq!(
plan.retags(),
3,
"the album tag change retags each in place"
);
assert_eq!(
plan.downloads(),
0,
"an album rename must never re-download"
);
assert_eq!(
plan.deletes(),
0,
"deletion safety: a rename deletes nothing"
);
for id in ["a", "b", "c"] {
assert!(plan.actions.contains(&Action::Rename {
from: format!("Creator/Old Album/{id}.flac"),
to: format!("Creator/New Album/{id}.flac"),
}));
}
}
#[test]
fn mis_rooted_clip_moves_never_deletes_even_when_deletion_is_armed() {
let mut manifest = Manifest::new();
manifest.insert(
"child",
entry("Creator/Root A/child.flac", AudioFormat::Flac, "m", "art"),
);
let d = vec![desired(
"child",
"Creator/Root B/child.flac",
AudioFormat::Flac,
"m",
"art",
)];
let plan = reconcile(&manifest, &d, &local_present("child"), &mirror_ok());
assert_eq!(
plan.actions,
vec![Action::Rename {
from: "Creator/Root A/child.flac".to_string(),
to: "Creator/Root B/child.flac".to_string(),
}],
"a mis-rooted clip is moved, not deleted or re-downloaded"
);
assert_eq!(
plan.deletes(),
0,
"deletion safety: a re-root deletes nothing"
);
assert_eq!(plan.downloads(), 0, "a re-root never re-fetches audio");
}
#[test]
fn format_change_reformats() {
let mut manifest = Manifest::new();
manifest.insert("a", entry("a.flac", AudioFormat::Flac, "m", "art"));
let d = vec![desired("a", "a.mp3", AudioFormat::Mp3, "m", "art")];
let plan = reconcile(&manifest, &d, &local_present("a"), &mirror_ok());
assert_eq!(
plan.actions,
vec![Action::Reformat {
clip: clip("a"),
path: "a.mp3".to_string(),
from_path: "a.flac".to_string(),
from: AudioFormat::Flac,
to: AudioFormat::Mp3,
}]
);
}
#[test]
fn format_change_takes_precedence_over_rename_and_retag() {
let mut manifest = Manifest::new();
manifest.insert(
"a",
entry("old/a.flac", AudioFormat::Flac, "old", "old-art"),
);
let d = vec![desired(
"a",
"new/a.mp3",
AudioFormat::Mp3,
"new",
"new-art",
)];
let plan = reconcile(&manifest, &d, &local_present("a"), &mirror_ok());
assert_eq!(plan.reformats(), 1);
assert_eq!(plan.renames(), 0);
assert_eq!(plan.retags(), 0);
}
#[test]
fn zero_length_file_downloads_even_when_hashes_match() {
let mut manifest = Manifest::new();
manifest.insert("a", entry("a.flac", AudioFormat::Flac, "m", "art"));
let local: HashMap<String, LocalFile> = [(
"a".to_string(),
LocalFile {
exists: true,
size: 0,
},
)]
.into_iter()
.collect();
let d = vec![desired("a", "a.flac", AudioFormat::Flac, "m", "art")];
let plan = reconcile(&manifest, &d, &local, &mirror_ok());
assert_eq!(plan.downloads(), 1);
assert_eq!(plan.skips(), 0);
}
#[test]
fn missing_file_downloads_even_when_hashes_match() {
let mut manifest = Manifest::new();
manifest.insert("a", entry("a.flac", AudioFormat::Flac, "m", "art"));
let local: HashMap<String, LocalFile> = [(
"a".to_string(),
LocalFile {
exists: false,
size: 0,
},
)]
.into_iter()
.collect();
let d = vec![desired("a", "a.flac", AudioFormat::Flac, "m", "art")];
let plan = reconcile(&manifest, &d, &local, &mirror_ok());
assert_eq!(plan.downloads(), 1);
}
#[test]
fn absent_local_probe_treated_as_missing() {
let mut manifest = Manifest::new();
manifest.insert("a", entry("a.flac", AudioFormat::Flac, "m", "art"));
let d = vec![desired("a", "a.flac", AudioFormat::Flac, "m", "art")];
let plan = reconcile(&manifest, &d, &HashMap::new(), &mirror_ok());
assert_eq!(plan.downloads(), 1);
}
#[test]
fn missing_file_download_wins_over_format_difference() {
let mut manifest = Manifest::new();
manifest.insert("a", entry("a.flac", AudioFormat::Flac, "m", "art"));
let local: HashMap<String, LocalFile> = [(
"a".to_string(),
LocalFile {
exists: false,
size: 0,
},
)]
.into_iter()
.collect();
let d = vec![desired("a", "a.mp3", AudioFormat::Mp3, "m", "art")];
let plan = reconcile(&manifest, &d, &local, &mirror_ok());
assert_eq!(plan.downloads(), 1);
assert_eq!(plan.reformats(), 0);
}
#[test]
fn duplicate_desired_unions_modes() {
let mut manifest = Manifest::new();
manifest.insert("a", entry("a.flac", AudioFormat::Flac, "m", "art"));
let mut mirror_entry = desired("a", "a.flac", AudioFormat::Flac, "m", "art");
mirror_entry.modes = vec![SourceMode::Mirror];
mirror_entry.trashed = true;
let mut copy_entry = desired("a", "a.flac", AudioFormat::Flac, "m", "art");
copy_entry.modes = vec![SourceMode::Copy];
let plan = reconcile(
&manifest,
&[mirror_entry, copy_entry],
&local_present("a"),
&mirror_ok(),
);
assert_eq!(plan.deletes(), 0);
}
#[test]
fn private_new_clip_downloads() {
let manifest = Manifest::new();
let mut d = desired("a", "a.flac", AudioFormat::Flac, "m", "art");
d.private = true;
let plan = reconcile(&manifest, &[d], &HashMap::new(), &mirror_ok());
assert_eq!(plan.downloads(), 1);
}
#[test]
fn private_zero_length_file_redownloads() {
let mut manifest = Manifest::new();
manifest.insert("a", entry("a.flac", AudioFormat::Flac, "m", "art"));
let local: HashMap<String, LocalFile> = [(
"a".to_string(),
LocalFile {
exists: true,
size: 0,
},
)]
.into_iter()
.collect();
let mut d = desired("a", "a.flac", AudioFormat::Flac, "m", "art");
d.private = true;
let plan = reconcile(&manifest, &[d], &local, &mirror_ok());
assert_eq!(plan.downloads(), 1);
}
#[test]
fn private_meta_change_retags() {
let mut manifest = Manifest::new();
manifest.insert("a", entry("a.flac", AudioFormat::Flac, "old", "art"));
let mut d = desired("a", "a.flac", AudioFormat::Flac, "new", "art");
d.private = true;
let plan = reconcile(&manifest, &[d], &local_present("a"), &mirror_ok());
assert_eq!(plan.retags(), 1);
assert_eq!(plan.deletes(), 0);
}
#[test]
fn output_is_deterministic_regardless_of_input_order() {
let mut manifest = Manifest::new();
manifest.insert("a", entry("a.flac", AudioFormat::Flac, "m", "art"));
manifest.insert("b", entry("b.flac", AudioFormat::Flac, "old", "art"));
manifest.insert("z", entry("z.flac", AudioFormat::Flac, "m", "art"));
let local: HashMap<String, LocalFile> = ["a", "b", "z"]
.iter()
.map(|id| (id.to_string(), present(100)))
.collect();
let forward = vec![
desired("a", "a.flac", AudioFormat::Flac, "m", "art"),
desired("b", "b.flac", AudioFormat::Flac, "new", "art"),
desired("c", "c.flac", AudioFormat::Flac, "m", "art"),
];
let mut reversed = forward.clone();
reversed.reverse();
let p1 = reconcile(&manifest, &forward, &local, &mirror_ok());
let p2 = reconcile(&manifest, &reversed, &local, &mirror_ok());
assert_eq!(p1.actions, p2.actions);
let ids: Vec<&str> = p1
.actions
.iter()
.map(|a| match a {
Action::Skip { clip_id } => clip_id.as_str(),
Action::Retag { clip, .. } => clip.id.as_str(),
Action::Download { clip, .. } => clip.id.as_str(),
Action::Delete { clip_id, .. } => clip_id.as_str(),
Action::Reformat { clip, .. } => clip.id.as_str(),
Action::Rename { to, .. } => to.as_str(),
Action::WriteArtifact { owner_id, .. }
| Action::DeleteArtifact { owner_id, .. }
| Action::MoveArtifact { owner_id, .. } => owner_id.as_str(),
Action::WriteStem { clip_id, .. }
| Action::DeleteStem { clip_id, .. }
| Action::MoveStem { clip_id, .. } => clip_id.as_str(),
})
.collect();
assert_eq!(ids, ["a", "b", "c", "z"]);
}
#[test]
fn empty_inputs_do_not_panic() {
let plan = reconcile(&Manifest::new(), &[], &HashMap::new(), &[]);
assert!(plan.is_empty());
assert_eq!(plan.len(), 0);
}
#[test]
fn empty_desired_with_full_manifest_deletes_all() {
let mut manifest = Manifest::new();
manifest.insert("a", entry("a.flac", AudioFormat::Flac, "m", "art"));
manifest.insert("b", entry("b.flac", AudioFormat::Flac, "m", "art"));
let plan = reconcile(&manifest, &[], &HashMap::new(), &mirror_ok());
assert_eq!(plan.deletes(), 2);
}
#[test]
fn full_desired_with_empty_manifest_downloads_all() {
let d = vec![
desired("a", "a.flac", AudioFormat::Flac, "m", "art"),
desired("b", "b.flac", AudioFormat::Flac, "m", "art"),
];
let plan = reconcile(&Manifest::new(), &d, &HashMap::new(), &mirror_ok());
assert_eq!(plan.downloads(), 2);
}
#[test]
fn plan_counts_sum_to_len() {
let mut manifest = Manifest::new();
manifest.insert("skip", entry("skip.flac", AudioFormat::Flac, "m", "art"));
manifest.insert(
"retag",
entry("retag.flac", AudioFormat::Flac, "old", "art"),
);
manifest.insert(
"reformat",
entry("reformat.flac", AudioFormat::Flac, "m", "art"),
);
manifest.insert(
"rename",
entry("old/rename.flac", AudioFormat::Flac, "m", "art"),
);
manifest.insert("gone", entry("gone.flac", AudioFormat::Flac, "m", "art"));
let local: HashMap<String, LocalFile> = ["skip", "retag", "reformat", "rename", "gone"]
.iter()
.map(|id| (id.to_string(), present(100)))
.collect();
let d = vec![
desired("skip", "skip.flac", AudioFormat::Flac, "m", "art"),
desired("retag", "retag.flac", AudioFormat::Flac, "new", "art"),
desired("reformat", "reformat.mp3", AudioFormat::Mp3, "m", "art"),
desired("rename", "new/rename.flac", AudioFormat::Flac, "m", "art"),
desired("download", "download.flac", AudioFormat::Flac, "m", "art"),
];
let plan = reconcile(&manifest, &d, &local, &mirror_ok());
let summed = plan.downloads()
+ plan.reformats()
+ plan.retags()
+ plan.renames()
+ plan.deletes()
+ plan.skips();
assert_eq!(summed, plan.len());
assert_eq!(plan.downloads(), 1);
assert_eq!(plan.reformats(), 1);
assert_eq!(plan.retags(), 1);
assert_eq!(plan.renames(), 1);
assert_eq!(plan.deletes(), 1);
assert_eq!(plan.skips(), 1);
}