use super::*;
#[test]
fn stem_path_drift_emits_move() {
let mut manifest = Manifest::new();
manifest.insert(
"a",
entry_with_stems("a", &[("voc", "old.stems/voc.mp3", "h1")]),
);
let d = vec![stem_desired(
"a",
Some(vec![dstem("voc", "new.stems/voc.mp3", "h1")]),
)];
let local: HashMap<String, LocalFile> = [
("a".to_string(), present(100)),
("old.stems/voc.mp3".to_string(), present(50)),
]
.into_iter()
.collect();
let plan = reconcile(&manifest, &d, &local, &mirror_ok());
assert_eq!(plan.stem_moves(), 1);
assert_eq!(plan.stem_writes(), 0);
assert!(plan.actions.contains(&Action::MoveStem {
clip_id: "a".to_string(),
key: "voc".to_string(),
stem_id: "voc".to_string(),
from: "old.stems/voc.mp3".to_string(),
to: "new.stems/voc.mp3".to_string(),
source_url: "https://cdn1.suno.ai/voc.mp3".to_string(),
format: StemFormat::Mp3,
hash: "h1".to_string(),
}));
}
fn dstem(key: &str, path: &str, hash: &str) -> DesiredStem {
DesiredStem {
key: key.to_string(),
stem_id: key.to_string(),
path: path.to_string(),
source_url: format!("https://cdn1.suno.ai/{key}.mp3"),
format: StemFormat::Mp3,
hash: hash.to_string(),
}
}
fn stem_desired(id: &str, stems: Option<Vec<DesiredStem>>) -> Desired {
Desired {
stems,
..desired(id, &format!("{id}.flac"), AudioFormat::Flac, "m", "art")
}
}
fn entry_with_stems(id: &str, stems: &[(&str, &str, &str)]) -> ManifestEntry {
let mut e = entry(&format!("{id}.flac"), AudioFormat::Flac, "m", "art");
for (key, path, hash) in stems {
e.stems.insert(
key.to_string(),
ArtifactState {
path: path.to_string(),
hash: hash.to_string(),
},
);
}
e
}
fn stem_writes(plan: &Plan) -> Vec<(&str, &str)> {
plan.actions
.iter()
.filter_map(|a| match a {
Action::WriteStem { key, path, .. } => Some((key.as_str(), path.as_str())),
_ => None,
})
.collect()
}
fn stem_deletes(plan: &Plan) -> Vec<(&str, &str)> {
plan.actions
.iter()
.filter_map(|a| match a {
Action::DeleteStem { key, path, .. } => Some((key.as_str(), path.as_str())),
_ => None,
})
.collect()
}
#[test]
fn stems_none_keeps_every_existing_stem() {
let mut manifest = Manifest::new();
manifest.insert(
"a",
entry_with_stems(
"a",
&[
("voc", "a.stems/voc.mp3", "h1"),
("drm", "a.stems/drm.mp3", "h2"),
],
),
);
let d = vec![stem_desired("a", None)];
let plan = reconcile(&manifest, &d, &local_present("a"), &mirror_ok());
assert_eq!(plan.stem_writes(), 0);
assert_eq!(plan.stem_deletes(), 0);
}
#[test]
fn stems_authoritative_writes_missing_stems() {
let mut manifest = Manifest::new();
manifest.insert("a", entry("a.flac", AudioFormat::Flac, "m", "art"));
let d = vec![stem_desired(
"a",
Some(vec![
dstem("voc", "a.stems/voc.mp3", "h1"),
dstem("drm", "a.stems/drm.mp3", "h2"),
]),
)];
let plan = reconcile(&manifest, &d, &local_present("a"), &mirror_ok());
assert_eq!(
stem_writes(&plan),
vec![("voc", "a.stems/voc.mp3"), ("drm", "a.stems/drm.mp3")]
);
assert_eq!(plan.stem_deletes(), 0);
}
#[test]
fn stems_authoritative_rewrites_only_on_hash_or_path_drift() {
let mut manifest = Manifest::new();
manifest.insert(
"a",
entry_with_stems(
"a",
&[
("voc", "a.stems/voc.mp3", "h1"),
("drm", "a.stems/drm.mp3", "h2"),
("bas", "old.stems/bas.mp3", "h3"),
],
),
);
let d = vec![stem_desired(
"a",
Some(vec![
dstem("voc", "a.stems/voc.mp3", "h1"), dstem("drm", "a.stems/drm.mp3", "h2-new"), dstem("bas", "a.stems/bas.mp3", "h3"), ]),
)];
let plan = reconcile(&manifest, &d, &local_present("a"), &mirror_ok());
assert_eq!(
stem_writes(&plan),
vec![("drm", "a.stems/drm.mp3"), ("bas", "a.stems/bas.mp3")]
);
assert_eq!(plan.stem_deletes(), 0);
}
#[test]
fn stems_authoritative_removes_a_stem_absent_from_the_set() {
let mut manifest = Manifest::new();
manifest.insert(
"a",
entry_with_stems(
"a",
&[
("voc", "a.stems/voc.mp3", "h1"),
("drm", "a.stems/drm.mp3", "h2"),
],
),
);
let d = vec![stem_desired(
"a",
Some(vec![dstem("voc", "a.stems/voc.mp3", "h1")]),
)];
let plan = reconcile(&manifest, &d, &local_present("a"), &mirror_ok());
assert_eq!(plan.stem_writes(), 0);
assert_eq!(stem_deletes(&plan), vec![("drm", "a.stems/drm.mp3")]);
}
#[test]
fn stems_removal_needs_deletion_allowed() {
let mut manifest = Manifest::new();
manifest.insert(
"a",
entry_with_stems(
"a",
&[
("voc", "a.stems/voc.mp3", "h1"),
("drm", "a.stems/drm.mp3", "h2"),
],
),
);
let d = vec![stem_desired(
"a",
Some(vec![dstem("voc", "a.stems/voc.mp3", "h1")]),
)];
let incomplete = vec![SourceStatus {
mode: SourceMode::Mirror,
fully_enumerated: false,
}];
assert_eq!(
reconcile(&manifest, &d, &local_present("a"), &incomplete).stem_deletes(),
0
);
let copy_only = vec![SourceStatus {
mode: SourceMode::Copy,
fully_enumerated: true,
}];
assert_eq!(
reconcile(&manifest, &d, &local_present("a"), ©_only).stem_deletes(),
0
);
}
#[test]
fn stems_removal_skipped_for_preserved_or_protected_clip() {
let mut manifest = Manifest::new();
let mut e = entry_with_stems(
"a",
&[
("voc", "a.stems/voc.mp3", "h1"),
("drm", "a.stems/drm.mp3", "h2"),
],
);
e.preserve = true;
manifest.insert("a", e);
let authoritative = Some(vec![dstem("voc", "a.stems/voc.mp3", "h1")]);
let d = vec![stem_desired("a", authoritative.clone())];
assert_eq!(
reconcile(&manifest, &d, &local_present("a"), &mirror_ok()).stem_deletes(),
0
);
let mut manifest2 = Manifest::new();
manifest2.insert(
"a",
entry_with_stems(
"a",
&[
("voc", "a.stems/voc.mp3", "h1"),
("drm", "a.stems/drm.mp3", "h2"),
],
),
);
let held = Desired {
modes: vec![SourceMode::Mirror, SourceMode::Copy],
stems: authoritative,
..desired("a", "a.flac", AudioFormat::Flac, "m", "art")
};
assert_eq!(
reconcile(&manifest2, &[held], &local_present("a"), &mirror_ok()).stem_deletes(),
0
);
}
#[test]
fn stems_are_co_deleted_when_the_song_is_trashed() {
let mut manifest = Manifest::new();
manifest.insert(
"a",
entry_with_stems(
"a",
&[
("voc", "a.stems/voc.mp3", "h1"),
("drm", "a.stems/drm.mp3", "h2"),
],
),
);
let trashed = Desired {
trashed: true,
..desired("a", "a.flac", AudioFormat::Flac, "m", "art")
};
let plan = reconcile(&manifest, &[trashed], &local_present("a"), &mirror_ok());
assert_eq!(plan.deletes(), 1, "the trashed audio is deleted");
let mut deleted: Vec<&str> = stem_deletes(&plan).into_iter().map(|(k, _)| k).collect();
deleted.sort_unstable();
assert_eq!(deleted, vec!["drm", "voc"], "both stems co-deleted");
}
#[test]
fn stems_are_co_deleted_for_an_absent_clip() {
let mut manifest = Manifest::new();
manifest.insert(
"a",
entry_with_stems("a", &[("voc", "a.stems/voc.mp3", "h1")]),
);
let plan = reconcile(&manifest, &[], &local_present("a"), &mirror_ok());
assert_eq!(plan.deletes(), 1);
assert_eq!(stem_deletes(&plan), vec![("voc", "a.stems/voc.mp3")]);
}
#[test]
fn stems_are_kept_when_absent_clip_listing_is_incomplete() {
let mut manifest = Manifest::new();
manifest.insert(
"a",
entry_with_stems("a", &[("voc", "a.stems/voc.mp3", "h1")]),
);
let incomplete = vec![SourceStatus {
mode: SourceMode::Mirror,
fully_enumerated: false,
}];
let plan = reconcile(&manifest, &[], &HashMap::new(), &incomplete);
assert_eq!(plan.deletes(), 0);
assert_eq!(plan.stem_deletes(), 0);
}
#[test]
fn stem_delete_is_suppressed_when_it_aliases_a_stem_write() {
let mut manifest = Manifest::new();
manifest.insert(
"a",
entry_with_stems("a", &[("old", "a.stems/mix.mp3", "h1")]),
);
let d = vec![stem_desired(
"a",
Some(vec![dstem("new", "a.stems/mix.mp3", "h2")]),
)];
let plan = reconcile(&manifest, &d, &local_present("a"), &mirror_ok());
assert_eq!(stem_writes(&plan), vec![("new", "a.stems/mix.mp3")]);
assert!(
!plan.actions.iter().any(|a| matches!(
a,
Action::DeleteStem { path, .. } if path == "a.stems/mix.mp3"
)),
"a stem delete must never alias a stem write target"
);
}
fn stems_entry_at(audio: &str, stems: &[(&str, &str, &str)]) -> ManifestEntry {
let mut e = entry(audio, AudioFormat::Flac, "m", "art");
for (key, path, hash) in stems {
e.stems.insert(
key.to_string(),
ArtifactState {
path: path.to_string(),
hash: hash.to_string(),
},
);
}
e
}
fn stems_none_at(id: &str, new_path: &str) -> Desired {
Desired {
stems: None,
..desired(id, new_path, AudioFormat::Flac, "m", "art")
}
}
#[test]
fn stems_none_relocates_stranded_stems_on_retitle() {
let mut manifest = Manifest::new();
manifest.insert(
"a",
stems_entry_at(
"Old.flac",
&[
("voc", "Old.stems/voc.mp3", "h1"),
("drm", "Old.stems/drm.mp3", "h2"),
],
),
);
let d = vec![stems_none_at("a", "New.flac")];
let local: HashMap<String, LocalFile> = [
("a".to_string(), present(100)),
("Old.stems/voc.mp3".to_string(), present(50)),
("Old.stems/drm.mp3".to_string(), present(50)),
]
.into_iter()
.collect();
let plan = reconcile(&manifest, &d, &local, &mirror_ok());
assert_eq!(plan.stem_moves(), 2);
assert_eq!(plan.stem_writes(), 0);
assert_eq!(plan.stem_deletes(), 0);
assert!(plan.actions.contains(&Action::MoveStem {
clip_id: "a".to_string(),
key: "voc".to_string(),
stem_id: String::new(),
from: "Old.stems/voc.mp3".to_string(),
to: "New.stems/voc.mp3".to_string(),
source_url: String::new(),
format: StemFormat::Mp3,
hash: "h1".to_string(),
}));
assert!(plan.actions.contains(&Action::MoveStem {
clip_id: "a".to_string(),
key: "drm".to_string(),
stem_id: String::new(),
from: "Old.stems/drm.mp3".to_string(),
to: "New.stems/drm.mp3".to_string(),
source_url: String::new(),
format: StemFormat::Mp3,
hash: "h2".to_string(),
}));
}
#[test]
fn stems_none_relocation_skipped_when_old_stem_absent() {
let mut manifest = Manifest::new();
manifest.insert(
"a",
stems_entry_at("Old.flac", &[("voc", "Old.stems/voc.mp3", "h1")]),
);
let d = vec![stems_none_at("a", "New.flac")];
let plan = reconcile(&manifest, &d, &local_present("a"), &mirror_ok());
assert_eq!(plan.stem_moves(), 0);
assert_eq!(plan.stem_writes(), 0);
assert_eq!(plan.stem_deletes(), 0);
}
#[test]
fn stems_none_no_relocation_without_retitle() {
let mut manifest = Manifest::new();
manifest.insert(
"a",
stems_entry_at("a.flac", &[("voc", "a.stems/voc.mp3", "h1")]),
);
let d = vec![stem_desired("a", None)];
let local: HashMap<String, LocalFile> = [
("a".to_string(), present(100)),
("a.stems/voc.mp3".to_string(), present(50)),
]
.into_iter()
.collect();
let plan = reconcile(&manifest, &d, &local, &mirror_ok());
assert_eq!(plan.stem_moves(), 0);
assert_eq!(plan.stem_writes(), 0);
assert_eq!(plan.stem_deletes(), 0);
}
#[test]
fn stems_none_not_relocated_for_trashed_clip() {
let mut manifest = Manifest::new();
manifest.insert(
"a",
stems_entry_at("Old.flac", &[("voc", "Old.stems/voc.mp3", "h1")]),
);
let d = vec![Desired {
trashed: true,
..stems_none_at("a", "New.flac")
}];
let local: HashMap<String, LocalFile> = [
("a".to_string(), present(100)),
("Old.stems/voc.mp3".to_string(), present(50)),
]
.into_iter()
.collect();
let not_enumerated = vec![SourceStatus {
mode: SourceMode::Mirror,
fully_enumerated: false,
}];
let plan = reconcile(&manifest, &d, &local, ¬_enumerated);
assert_eq!(plan.stem_moves(), 0, "no strand inversion");
assert_eq!(plan.stem_deletes(), 0);
assert_eq!(plan.deletes(), 0);
}
#[test]
fn two_clip_title_swap_with_stems_is_clobber_safe() {
let mut manifest = Manifest::new();
manifest.insert(
"a",
stems_entry_at("A.flac", &[("voc", "A.stems/voc.mp3", "h1")]),
);
manifest.insert(
"b",
stems_entry_at("B.flac", &[("voc", "B.stems/voc.mp3", "h2")]),
);
let d = vec![stems_none_at("a", "B.flac"), stems_none_at("b", "A.flac")];
let local: HashMap<String, LocalFile> = [
("a".to_string(), present(100)),
("b".to_string(), present(100)),
("A.stems/voc.mp3".to_string(), present(50)),
("B.stems/voc.mp3".to_string(), present(50)),
]
.into_iter()
.collect();
let plan = reconcile(&manifest, &d, &local, &mirror_ok());
assert_eq!(
plan.stem_moves(),
0,
"both colliding stem moves are suppressed to Skip"
);
assert_eq!(plan.stem_deletes(), 0, "no stem is lost");
}