fn now() -> std::time::SystemTime {
std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_700_000_000)
}
#[tokio::test]
async fn fast_rule_priority_last_config_rule_wins_for_pin() {
let t = crate::Torrent {
category: "linux".into(),
..crate::Torrent::zero()
};
let c = crate::Config {
rules: vec![
crate::Rule {
match_: crate::Match {
categories: Some(["linux".into()].into()),
..Default::default()
},
pin: Some(true),
..Default::default()
},
crate::Rule {
match_: crate::Match {
categories: Some(["linux".into()].into()),
..Default::default()
},
pin: Some(false),
..Default::default()
},
],
..crate::Config::empty()
};
let g = crate::qbt_mock::global_with_trackers(c, now(), vec![]);
let res = g.rules(&t, false).await.unwrap();
assert!(res.pinned.is_none());
}
#[tokio::test]
async fn fast_rules_compose_across_independent_fields() {
let t = crate::Torrent {
category: "linux".into(),
..crate::Torrent::zero()
};
let c = crate::Config {
rules: vec![
crate::Rule {
match_: crate::Match {
categories: Some(["linux".into()].into()),
..Default::default()
},
include_in_score: Some(false),
..Default::default()
},
crate::Rule {
match_: crate::Match {
categories: Some(["linux".into()].into()),
..Default::default()
},
pin: Some(true),
..Default::default()
},
],
..crate::Config::empty()
};
let g = crate::qbt_mock::global_with_trackers(c, now(), vec![]);
let res = g.rules(&t, false).await.unwrap();
assert!(matches!(res.pinned, Some(crate::PinReason::Explicit)));
assert!(!res.include_in_score);
}
#[tokio::test]
async fn slow_phase2_resolves_when_phase1_has_multiple_possible_pin_values() {
let t = crate::Torrent {
category: "linux".into(),
seeding_time: std::time::Duration::from_secs(1),
..crate::Torrent::zero()
};
let c = crate::Config {
categories_allowed: ["linux".into()].into(),
rules: vec![
crate::Rule {
match_: crate::Match {
tracker_msg: Some(regex::RegexSet::new([r"^ok$"]).unwrap()),
..Default::default()
},
pin: Some(true),
..Default::default()
},
crate::Rule {
match_: crate::Match {
tracker_url: Some(regex::RegexSet::new([r"^http://bad/.*$"]).unwrap()),
..Default::default()
},
pin: Some(false),
..Default::default()
},
],
..crate::Config::empty()
};
let g = crate::qbt_mock::global_with_trackers(c.clone(), now(), vec![
crate::Tracker {
msg: "ok".into(),
status: crate::TrackerStatus::Working,
url: "http://t/".into(),
},
]);
let res = g.rules(&t, false).await.unwrap();
assert!(matches!(res.pinned, Some(crate::PinReason::Explicit)));
let g = crate::qbt_mock::global_with_trackers(c.clone(), now(), vec![
crate::Tracker {
msg: "nope".into(),
status: crate::TrackerStatus::Working,
url: "http://bad/x".into(),
},
]);
let res = g.rules(&t, false).await.unwrap();
assert!(res.pinned.is_none());
let g = crate::qbt_mock::global_with_trackers(c.clone(), now(), vec![
crate::Tracker {
msg: "ok".into(),
status: crate::TrackerStatus::Working,
url: "http://bad/x".into(),
},
]);
let res = g.rules(&t, false).await.unwrap();
assert!(res.pinned.is_none());
let g = crate::qbt_mock::global_with_trackers(c, now(), vec![
crate::Tracker {
msg: "nope".into(),
status: crate::TrackerStatus::Working,
url: "http://t/".into(),
},
]);
let res = g.rules(&t, false).await.unwrap();
assert!(res.pinned.is_none());
}
#[tokio::test]
async fn pin_reason_only_from_matching_rule() {
let t = crate::Torrent {
category: "linux".into(),
..crate::Torrent::zero()
};
let non_matching = crate::Rule {
match_: crate::Match {
categories: Some(["mac".into()].into()),
..Default::default()
},
pin: Some(true),
pin_reason: Some("should-not-leak".into()),
..Default::default()
};
let matching = crate::Rule {
match_: crate::Match {
categories: Some(["linux".into()].into()),
..Default::default()
},
pin: Some(true),
pin_reason: Some("keep linux".into()),
..Default::default()
};
let c = crate::Config {
rules: vec![non_matching.clone(), matching.clone()],
..crate::Config::empty()
};
let g = crate::qbt_mock::global_with_trackers(c, now(), vec![]);
let res = g.rules(&t, false).await.unwrap();
assert_eq!(res.pinned, Some(crate::PinReason::Explicit));
assert_eq!(res.pin_reason, Some("keep linux".into()));
let c = crate::Config {
rules: vec![matching, non_matching],
..crate::Config::empty()
};
let g = crate::qbt_mock::global_with_trackers(c, now(), vec![]);
let res = g.rules(&t, false).await.unwrap();
assert_eq!(res.pinned, Some(crate::PinReason::Explicit));
assert_eq!(res.pin_reason, Some("keep linux".into()));
}
#[tokio::test]
async fn pin_reason_from_applied_pin_surrounded_by_opposite() {
let t = crate::Torrent {
category: "linux".into(),
..crate::Torrent::zero()
};
let c = crate::Config {
rules: vec![
crate::Rule {
match_: crate::Match {
categories: Some(["linux".into()].into()),
..Default::default()
},
pin: Some(true),
pin_reason: Some("before".into()),
..Default::default()
},
crate::Rule {
match_: crate::Match {
categories: Some(["linux".into()].into()),
..Default::default()
},
pin: Some(false),
pin_reason: Some("applied".into()),
..Default::default()
},
crate::Rule {
match_: crate::Match {
categories: Some(["mac".into()].into()),
..Default::default()
},
pin: Some(true),
pin_reason: Some("after".into()),
..Default::default()
},
],
..crate::Config::empty()
};
let g = crate::qbt_mock::global_with_trackers(c, now(), vec![]);
let res = g.rules(&t, false).await.unwrap();
assert!(res.pinned.is_none());
assert_eq!(res.pin_reason, Some("applied".into()));
}
#[tokio::test]
async fn pin_reason_from_applied_pin_true_surrounded_by_false() {
let t = crate::Torrent {
category: "linux".into(),
..crate::Torrent::zero()
};
let c = crate::Config {
rules: vec![
crate::Rule {
match_: crate::Match {
categories: Some(["linux".into()].into()),
..Default::default()
},
pin: Some(false),
pin_reason: Some("before".into()),
..Default::default()
},
crate::Rule {
match_: crate::Match {
categories: Some(["linux".into()].into()),
..Default::default()
},
pin: Some(true),
pin_reason: Some("applied".into()),
..Default::default()
},
crate::Rule {
match_: crate::Match {
categories: Some(["mac".into()].into()),
..Default::default()
},
pin: Some(false),
pin_reason: Some("after".into()),
..Default::default()
},
],
..crate::Config::empty()
};
let g = crate::qbt_mock::global_with_trackers(c, now(), vec![]);
let res = g.rules(&t, false).await.unwrap();
assert_eq!(res.pinned, Some(crate::PinReason::Explicit));
assert_eq!(res.pin_reason, Some("applied".into()));
}
#[tokio::test]
async fn group_pinned_torrent_reports_no_pin_reason() {
let t = crate::Torrent {
category: "linux".into(),
..crate::Torrent::zero()
};
let c = crate::Config {
rules: vec![crate::Rule {
match_: crate::Match {
categories: Some(["linux".into()].into()),
..Default::default()
},
pin: Some(false),
pin_reason: Some("should-not-leak".into()),
..Default::default()
}],
..crate::Config::empty()
};
let g = crate::qbt_mock::global_with_trackers(c, now(), vec![]);
let res = g.rules(&t, true).await.unwrap();
assert_eq!(res.pinned, None);
assert_eq!(res.pin_reason, None);
}
#[tokio::test]
async fn pin_reason_from_slow_matched_rule() {
let t = crate::Torrent {
category: "linux".into(),
seeding_time: std::time::Duration::from_secs(1),
..crate::Torrent::zero()
};
let c = crate::Config {
categories_allowed: ["linux".into()].into(),
rules: vec![
crate::Rule {
match_: crate::Match {
tracker_url: Some(regex::RegexSet::new([r"^http://other/.*$"]).unwrap()),
..Default::default()
},
pin: Some(false),
pin_reason: Some("should-not-leak".into()),
..Default::default()
},
crate::Rule {
match_: crate::Match {
tracker_url: Some(regex::RegexSet::new([r"^http://keep/.*$"]).unwrap()),
..Default::default()
},
pin: Some(true),
pin_reason: Some("slow keep".into()),
..Default::default()
},
],
..crate::Config::empty()
};
let g = crate::qbt_mock::global_with_trackers(c, now(), vec![
crate::Tracker {
msg: "ok".into(),
status: crate::TrackerStatus::Working,
url: "http://keep/x".into(),
},
]);
let res = g.rules(&t, false).await.unwrap();
assert_eq!(res.pinned, Some(crate::PinReason::Explicit));
assert_eq!(res.pin_reason, Some("slow keep".into()));
}
#[tokio::test]
async fn mixed_fast_and_slow_rules() {
let t = crate::Torrent {
category: "linux".into(),
seeders: 5,
seeding_time: std::time::Duration::from_secs(1),
..crate::Torrent::zero()
};
let c = crate::Config {
categories_allowed: ["linux".into()].into(),
rules: vec![
crate::Rule {
match_: crate::Match {
categories: Some(["linux".into()].into()),
..Default::default()
},
include_in_score: Some(false),
..Default::default()
},
crate::Rule {
match_: crate::Match {
tracker_msg: Some(regex::RegexSet::new([r"^private$"]).unwrap()),
..Default::default()
},
seeder_count_min: Some(50),
..Default::default()
},
],
..crate::Config::empty()
};
let g = crate::qbt_mock::global_with_trackers(c.clone(), now(), vec![
crate::Tracker {
msg: "private".into(),
status: crate::TrackerStatus::Working,
url: "http://t/".into(),
},
]);
let res = g.rules(&t, false).await.unwrap();
assert!(matches!(res.pinned, Some(crate::PinReason::Seeders(5))));
assert!(!res.include_in_score);
let g = crate::qbt_mock::global_with_trackers(c, now(), vec![
crate::Tracker {
msg: "public".into(),
status: crate::TrackerStatus::Working,
url: "http://t/".into(),
},
]);
let res = g.rules(&t, false).await.unwrap();
assert!(res.pinned.is_none());
assert!(!res.include_in_score);
}