use crate::testing_prelude::*;
#[test]
fn torrent_auditor_execute_clean() {
let bytes = TorrentBuilder::new().with_multi_file(["song.flac"]).build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert_eq!(output.name, Some("album".to_owned()));
assert!(output.issues.is_none());
}
#[test]
fn torrent_auditor_execute_non_utf8() {
let component = splice(b"song", E_ACUTE, b".flac");
let bytes = TorrentBuilder::new().with_multi_file([component]).build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
let issue = output
.issues
.iter()
.flatten()
.find(|issue| issue.kind == AuditIssueKind::Path(AuditPathIssueKind::NonUtf8))
.expect("expected NonUtf8");
let suggestions = issue.suggestions.as_ref().expect("expected suggestions");
assert!(
suggestions
.iter()
.any(|suggestion| suggestion.value == "song\u{e9}.flac"),
"expected a windows-1252 suggestion, got: {suggestions:?}"
);
}
#[test]
fn torrent_auditor_execute_zero_width_space() {
let bytes = TorrentBuilder::new()
.with_multi_file(["song\u{200B}.flac"])
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert!(
output
.issues
.iter()
.flatten()
.any(|issue| issue.kind == AuditIssueKind::Path(AuditPathIssueKind::Invisible)),
"expected Invisible, got: {:?}",
output.issues
);
}
#[test]
fn torrent_auditor_execute_rtl_override() {
let bytes = TorrentBuilder::new()
.with_multi_file(["song\u{202E}.flac"])
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert!(
output
.issues
.iter()
.flatten()
.any(|issue| issue.kind == AuditIssueKind::Path(AuditPathIssueKind::LibtorrentStripped)),
"expected LibtorrentStripped, got: {:?}",
output.issues
);
}
#[test]
fn torrent_auditor_execute_unnecessary_directional() {
let bytes = TorrentBuilder::new()
.with_multi_file(["song\u{200E}.flac"])
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert_eq!(
output.get_issue_kinds(),
HashSet::from([
AuditIssueKind::Path(AuditPathIssueKind::LibtorrentStripped),
AuditIssueKind::Path(AuditPathIssueKind::UnnecessaryDirectional),
]),
);
}
#[test]
fn torrent_auditor_execute_unnecessary_directional_isolate() {
let bytes = TorrentBuilder::new()
.with_multi_file(["song\u{2066}.flac"])
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert_eq!(
output.get_issue_kinds(),
HashSet::from([AuditIssueKind::Path(
AuditPathIssueKind::UnnecessaryDirectional
)]),
);
}
#[test]
fn torrent_auditor_execute_directional_with_rtl() {
let bytes = TorrentBuilder::new()
.with_multi_file(["\u{200E}\u{05D0}song.flac"])
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert!(
!output.has_path_kind(AuditPathIssueKind::UnnecessaryDirectional),
"got: {:?}",
output.issues
);
}
#[test]
fn torrent_auditor_execute_ignore_unnecessary_directional() {
let bytes = TorrentBuilder::new()
.with_multi_file(["song\u{200E}.flac"])
.build();
let auditor = TorrentAuditor::new(AuditOptions {
ignore_directional: true,
..AuditOptions::default()
});
let output = auditor.execute_bytes(&bytes);
assert!(
!output.has_path_kind(AuditPathIssueKind::UnnecessaryDirectional),
"got: {:?}",
output.issues
);
}
#[test]
fn torrent_auditor_execute_traversal() {
let bytes = TorrentBuilder::new()
.with_multi_file(["..", "song.flac"])
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert!(
output
.issues
.iter()
.flatten()
.any(|issue| issue.kind == AuditIssueKind::Path(AuditPathIssueKind::UnsafeSegment)),
"expected UnsafeSegment, got: {:?}",
output.issues
);
}
#[test]
fn torrent_auditor_execute_forward_slash() {
let bytes = TorrentBuilder::new()
.with_multi_file(["song/evil.flac"])
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert_eq!(
output.get_issue_kinds(),
HashSet::from([
AuditIssueKind::Path(AuditPathIssueKind::Restricted),
AuditIssueKind::Path(AuditPathIssueKind::UnsafeSegment),
AuditIssueKind::Path(AuditPathIssueKind::LibtorrentStripped),
]),
);
}
#[test]
fn torrent_auditor_execute_trailing_slash() {
let bytes = TorrentBuilder::new().with_multi_file(["song/"]).build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert_eq!(
output.get_issue_kinds(),
HashSet::from([
AuditIssueKind::Path(AuditPathIssueKind::UnsafeSegment),
AuditIssueKind::Path(AuditPathIssueKind::Restricted),
AuditIssueKind::Path(AuditPathIssueKind::LibtorrentStripped),
]),
);
}
#[test]
fn torrent_auditor_execute_backslash() {
let bytes = TorrentBuilder::new()
.with_multi_file(["song\\evil.flac"])
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert_eq!(
output.get_issue_kinds(),
HashSet::from([
AuditIssueKind::Path(AuditPathIssueKind::Restricted),
AuditIssueKind::Path(AuditPathIssueKind::UnsafeSegment),
AuditIssueKind::Path(AuditPathIssueKind::LibtorrentStripped),
]),
);
}
#[test]
fn torrent_auditor_execute_decomposed() {
let bytes = TorrentBuilder::new()
.with_multi_file(["cafe\u{0301}.flac"])
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert_eq!(
output.get_issue_kinds(),
HashSet::from([AuditIssueKind::Path(AuditPathIssueKind::Decomposed)]),
);
}
#[test]
fn torrent_auditor_execute_composed() {
let bytes = TorrentBuilder::new()
.with_multi_file(["caf\u{00e9}.flac"])
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert!(output.issues.is_none(), "got: {:?}", output.issues);
}
#[test]
fn torrent_auditor_execute_ignore_invisible() {
let bytes = TorrentBuilder::new()
.with_multi_file(["song\u{200B}.flac"])
.build();
let auditor = TorrentAuditor::new(AuditOptions {
ignore_invisible: true,
..AuditOptions::default()
});
let output = auditor.execute_bytes(&bytes);
assert!(
!output.has_path_kind(AuditPathIssueKind::Invisible),
"got: {:?}",
output.issues
);
}
#[test]
fn torrent_auditor_execute_ignore_libtorrent() {
let bytes = TorrentBuilder::new()
.with_multi_file(["song\u{202E}.flac"])
.build();
let auditor = TorrentAuditor::new(AuditOptions {
ignore_libtorrent: true,
..AuditOptions::default()
});
let output = auditor.execute_bytes(&bytes);
assert!(
!output.has_path_kind(AuditPathIssueKind::LibtorrentStripped),
"got: {:?}",
output.issues
);
}
#[test]
fn torrent_auditor_execute_ignore_unsafe() {
let bytes = TorrentBuilder::new()
.with_multi_file(["..", "song.flac"])
.build();
let auditor = TorrentAuditor::new(AuditOptions {
ignore_unsafe: true,
..AuditOptions::default()
});
let output = auditor.execute_bytes(&bytes);
assert!(
!output.has_path_kind(AuditPathIssueKind::UnsafeSegment),
"got: {:?}",
output.issues
);
}
#[test]
fn torrent_auditor_execute_ignore_nfd() {
let bytes = TorrentBuilder::new()
.with_multi_file(["cafe\u{0301}.flac"])
.build();
let auditor = TorrentAuditor::new(AuditOptions {
ignore_nfd: true,
..AuditOptions::default()
});
let output = auditor.execute_bytes(&bytes);
assert!(
!output.has_path_kind(AuditPathIssueKind::Decomposed),
"got: {:?}",
output.issues
);
}
#[test]
fn torrent_auditor_execute_leading_period() {
let bytes = TorrentBuilder::new()
.with_multi_file([".song.flac"])
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert!(
output.has_path_kind(AuditPathIssueKind::LeadingPeriod),
"expected LeadingPeriod, got: {:?}",
output.issues
);
}
#[test]
fn torrent_auditor_execute_leading_space() {
let bytes = TorrentBuilder::new()
.with_multi_file([" song.flac"])
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert!(
output.has_path_kind(AuditPathIssueKind::LeadingSpace),
"expected LeadingSpace, got: {:?}",
output.issues
);
}
#[test]
fn torrent_auditor_execute_trailing_space() {
let bytes = TorrentBuilder::new()
.with_multi_file(["song.flac "])
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert!(
output.has_path_kind(AuditPathIssueKind::TrailingSpace),
"expected TrailingSpace, got: {:?}",
output.issues
);
}
#[test]
fn torrent_auditor_execute_ignore_leading_period() {
let bytes = TorrentBuilder::new()
.with_multi_file([".song.flac"])
.build();
let auditor = TorrentAuditor::new(AuditOptions {
ignore_leading_period: true,
..AuditOptions::default()
});
let output = auditor.execute_bytes(&bytes);
assert!(
!output.has_path_kind(AuditPathIssueKind::LeadingPeriod),
"got: {:?}",
output.issues
);
}
#[test]
fn torrent_auditor_execute_ignore_leading_space() {
let bytes = TorrentBuilder::new()
.with_multi_file([" song.flac"])
.build();
let auditor = TorrentAuditor::new(AuditOptions {
ignore_leading_space: true,
..AuditOptions::default()
});
let output = auditor.execute_bytes(&bytes);
assert!(
!output.has_path_kind(AuditPathIssueKind::LeadingSpace),
"got: {:?}",
output.issues
);
}
#[test]
fn torrent_auditor_execute_ignore_trailing_space() {
let bytes = TorrentBuilder::new()
.with_multi_file(["song.flac "])
.build();
let auditor = TorrentAuditor::new(AuditOptions {
ignore_trailing_space: true,
..AuditOptions::default()
});
let output = auditor.execute_bytes(&bytes);
assert!(
!output.has_path_kind(AuditPathIssueKind::TrailingSpace),
"got: {:?}",
output.issues
);
}
#[test]
fn torrent_auditor_execute_name_non_utf8() {
let name = splice(b"album", E_ACUTE, b"");
let bytes = TorrentBuilder::new()
.with_dictionary(
"info",
TorrentBuilder::new()
.with_dictionaries("files", vec![TorrentBuilder::file(["song.flac"])])
.with_string("name", name),
)
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert!(
output
.issues
.iter()
.flatten()
.any(|issue| issue.kind == AuditIssueKind::Path(AuditPathIssueKind::NonUtf8)),
"expected NonUtf8, got: {:?}",
output.issues
);
}
#[test]
fn torrent_auditor_execute_single_file() {
let bytes = TorrentBuilder::new()
.with_dictionary(
"info",
TorrentBuilder::new()
.with_integer("length", 10)
.with_string("name", "song.flac"),
)
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
let issues = output.issues.expect("expected issues");
assert!(
issues
.iter()
.any(|issue| issue.kind == AuditIssueKind::NoFiles),
"expected NoFiles, got: {issues:?}"
);
}
#[test]
fn torrent_auditor_execute_not_bencode() {
let bytes = b"this is not bencode".to_vec();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
let issues = output.issues.expect("expected issues");
assert!(
issues
.iter()
.any(|issue| issue.kind == AuditIssueKind::Parse)
);
}
#[test]
fn torrent_auditor_execute_cesu8() {
let mut component = b"track ".to_vec();
component.extend_from_slice(&[0xED, 0xA0, 0xBD, 0xED, 0xB8, 0x89]);
component.push(b' ');
component.extend_from_slice(&[0xED, 0xA0, 0xBE, 0xED, 0xB5, 0xB4]);
component.extend_from_slice(b".flac");
let bytes = TorrentBuilder::new().with_multi_file([component]).build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert_eq!(
output.get_issue_kinds(),
HashSet::from([AuditIssueKind::Path(AuditPathIssueKind::NonUtf8)]),
);
let issue = output
.issues
.iter()
.flatten()
.find(|issue| issue.kind == AuditIssueKind::Path(AuditPathIssueKind::NonUtf8))
.expect("expected NonUtf8");
let suggestions = issue.suggestions.as_ref().expect("expected suggestions");
let first = suggestions.first().expect("expected a suggestion");
assert_eq!(first.kind, AuditSuggestionKind::Cesu8);
assert_eq!(first.value, "track \u{1F609} \u{1F974}.flac");
}
#[test]
fn torrent_auditor_execute_comment() {
let url = "https://example.invalid/torrents.php?id=1&torrentid=2#torrent2";
let bytes = TorrentBuilder::new()
.with_string("comment", url)
.with_multi_file(["song.flac"])
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert_eq!(output.id, Some(2));
assert_eq!(output.url.as_deref(), Some(url));
}
#[test]
fn torrent_auditor_execute_broken_extension() {
let component = splice(b"song", I_ACUTE, b".flac");
let bytes = TorrentBuilder::new().with_multi_file([component]).build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert_eq!(
output.get_issue_kinds(),
HashSet::from([
AuditIssueKind::Path(AuditPathIssueKind::BrokenExtension),
AuditIssueKind::Path(AuditPathIssueKind::NonUtf8),
]),
);
}
#[test]
fn torrent_auditor_execute_broken_extension_midname() {
let component = splice(b"so", I_ACUTE, b"ng.flac");
let bytes = TorrentBuilder::new().with_multi_file([component]).build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert!(
!output.has_path_kind(AuditPathIssueKind::BrokenExtension),
"got: {:?}",
output.issues
);
}
#[test]
fn torrent_auditor_execute_broken_extension_directory() {
let directory = splice(b"dir", I_ACUTE, b"");
let bytes = TorrentBuilder::new()
.with_multi_file([directory, b"song.flac".to_vec()])
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert!(
!output.has_path_kind(AuditPathIssueKind::BrokenExtension),
"got: {:?}",
output.issues
);
}
#[test]
fn torrent_auditor_execute_ignore_broken_extension() {
let component = splice(b"song", I_ACUTE, b".flac");
let bytes = TorrentBuilder::new().with_multi_file([component]).build();
let auditor = TorrentAuditor::new(AuditOptions {
ignore_broken_extension: true,
..AuditOptions::default()
});
let output = auditor.execute_bytes(&bytes);
assert!(
!output.has_path_kind(AuditPathIssueKind::BrokenExtension),
"got: {:?}",
output.issues
);
}
#[test]
fn torrent_auditor_execute_name_utf8() {
let bytes = TorrentBuilder::new()
.with_dictionary(
"info",
TorrentBuilder::new()
.with_dictionaries("files", vec![TorrentBuilder::file(["song.flac"])])
.with_string("name", "legacy")
.with_string("name.utf-8", "chosen"),
)
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert_eq!(output.name, Some("chosen".to_owned()));
}
#[test]
fn torrent_auditor_execute_path_utf8() {
let bytes = TorrentBuilder::new()
.with_dictionary(
"info",
TorrentBuilder::new()
.with_dictionaries(
"files",
vec![
TorrentBuilder::file(["song.flac"])
.with_list("path.utf-8", ["song\u{200B}.flac"]),
],
)
.with_string("name", "album"),
)
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert_eq!(
output.get_issue_kinds(),
HashSet::from([AuditIssueKind::Path(AuditPathIssueKind::Invisible)]),
);
}
#[test]
fn torrent_auditor_execute_name_utf8_wrong_type() {
let bytes = TorrentBuilder::new()
.with_dictionary(
"info",
TorrentBuilder::new()
.with_dictionaries("files", vec![TorrentBuilder::file(["song.flac"])])
.with_string("name", "album")
.with_integer("name.utf-8", 5),
)
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert_eq!(
output.get_issue_kinds(),
HashSet::from([AuditIssueKind::NameDivergence]),
);
}
#[test]
fn torrent_auditor_execute_path_utf8_wrong_type() {
let bytes = TorrentBuilder::new()
.with_dictionary(
"info",
TorrentBuilder::new()
.with_dictionaries(
"files",
vec![
TorrentBuilder::file(["song.flac"]).with_string("path.utf-8", "song.flac"),
],
)
.with_string("name", "album"),
)
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert_eq!(
output.get_issue_kinds(),
HashSet::from([AuditIssueKind::PathDivergence]),
);
}
#[test]
fn torrent_auditor_execute_name_utf8_empty() {
let bytes = TorrentBuilder::new()
.with_dictionary(
"info",
TorrentBuilder::new()
.with_dictionaries("files", vec![TorrentBuilder::file(["song.flac"])])
.with_string("name", "album")
.with_string("name.utf-8", ""),
)
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert_eq!(
output.get_issue_kinds(),
HashSet::from([AuditIssueKind::NameEmpty]),
);
}
#[test]
fn torrent_auditor_execute_path_utf8_empty() {
let bytes = TorrentBuilder::new()
.with_dictionary(
"info",
TorrentBuilder::new()
.with_dictionaries(
"files",
vec![
TorrentBuilder::file(["song.flac"])
.with_list("path.utf-8", Vec::<RawString>::new()),
],
)
.with_string("name", "album"),
)
.build();
let output = TorrentAuditor::mock().execute_bytes(&bytes);
assert_eq!(
output.get_issue_kinds(),
HashSet::from([AuditIssueKind::PathEmpty]),
);
}