use super::signals::*;
use chrono::{Duration, Utc};
#[test]
fn keyword_empty_keywords() {
assert_eq!(keyword_relevance("some tweet text", &[], 40.0), 0.0);
}
#[test]
fn keyword_no_match() {
let keywords = vec!["rust".to_string(), "cli".to_string()];
assert_eq!(keyword_relevance("python is great", &keywords, 40.0), 0.0);
}
#[test]
fn keyword_single_word_match() {
let keywords = vec!["rust".to_string(), "cli".to_string()];
let score = keyword_relevance("I love Rust programming", &keywords, 40.0);
assert!((score - 20.0).abs() < 0.01);
}
#[test]
fn keyword_all_match() {
let keywords = vec!["rust".to_string(), "cli".to_string()];
let score = keyword_relevance("Building a Rust CLI tool", &keywords, 40.0);
assert!((score - 40.0).abs() < 0.01);
}
#[test]
fn keyword_multi_word_double_weight() {
let keywords = vec!["mac".to_string(), "menu bar apps".to_string()];
let score = keyword_relevance("I love mac menu bar apps for productivity", &keywords, 40.0);
assert!((score - 40.0).abs() < 0.01);
}
#[test]
fn keyword_multi_word_only() {
let keywords = vec!["mac".to_string(), "menu bar apps".to_string()];
let score = keyword_relevance("My mac is slow", &keywords, 40.0);
let expected = (1.0 / 3.0) * 40.0;
assert!((score - expected).abs() < 0.01);
}
#[test]
fn keyword_case_insensitive() {
let keywords = vec!["RUST".to_string()];
let score = keyword_relevance("rust is awesome", &keywords, 40.0);
assert!((score - 40.0).abs() < 0.01);
}
#[test]
fn follower_zero() {
assert_eq!(follower_score(0, 20.0), 0.0);
}
#[test]
fn follower_100() {
let score = follower_score(100, 20.0);
assert!((score - 8.0).abs() < 0.1);
}
#[test]
fn follower_1000() {
let score = follower_score(1000, 20.0);
assert!((score - 12.0).abs() < 0.1);
}
#[test]
fn follower_10000() {
let score = follower_score(10000, 20.0);
assert!((score - 16.0).abs() < 0.1);
}
#[test]
fn follower_100000() {
let score = follower_score(100000, 20.0);
assert!((score - 20.0).abs() < 0.1);
}
#[test]
fn follower_million_clamped() {
let score = follower_score(1_000_000, 20.0);
assert!((score - 20.0).abs() < 0.01);
}
#[test]
fn recency_1_minute_ago() {
let now = Utc::now();
let created = (now - Duration::minutes(1)).to_rfc3339();
let score = recency_score_at(&created, 15.0, now);
assert!((score - 15.0).abs() < 0.5);
}
#[test]
fn recency_15_minutes_ago() {
let now = Utc::now();
let created = (now - Duration::minutes(15)).to_rfc3339();
let score = recency_score_at(&created, 15.0, now);
assert!(score > 12.0 && score < 15.0);
}
#[test]
fn recency_45_minutes_ago() {
let now = Utc::now();
let created = (now - Duration::minutes(45)).to_rfc3339();
let score = recency_score_at(&created, 15.0, now);
assert!(score > 7.0 && score < 12.0);
}
#[test]
fn recency_3_hours_ago() {
let now = Utc::now();
let created = (now - Duration::hours(3)).to_rfc3339();
let score = recency_score_at(&created, 15.0, now);
assert!(score > 3.0 && score < 8.0);
}
#[test]
fn recency_12_hours_ago() {
let now = Utc::now();
let created = (now - Duration::hours(12)).to_rfc3339();
let score = recency_score_at(&created, 15.0, now);
assert!((score - 0.0).abs() < 0.01);
}
#[test]
fn recency_invalid_timestamp() {
let now = Utc::now();
let score = recency_score_at("not-a-timestamp", 15.0, now);
assert_eq!(score, 0.0);
}
#[test]
fn engagement_zero() {
let score = engagement_rate(0, 0, 0, 1000, 25.0);
assert_eq!(score, 0.0);
}
#[test]
fn engagement_average_1_5_percent() {
let score = engagement_rate(15, 0, 0, 1000, 25.0);
assert!((score - 7.5).abs() < 0.1);
}
#[test]
fn engagement_high_5_percent() {
let score = engagement_rate(50, 0, 0, 1000, 25.0);
assert!((score - 25.0).abs() < 0.1);
}
#[test]
fn engagement_above_ceiling() {
let score = engagement_rate(100, 0, 0, 1000, 25.0);
assert!((score - 25.0).abs() < 0.01);
}
#[test]
fn engagement_zero_followers() {
let score = engagement_rate(10, 5, 2, 0, 25.0);
assert!((score - 25.0).abs() < 0.01);
}
#[test]
fn engagement_all_metrics() {
let score = engagement_rate(10, 5, 3, 1000, 25.0);
assert!((score - 9.0).abs() < 0.1);
}
#[test]
fn reply_count_zero_replies_max_score() {
let score = reply_count_score(0, 15.0);
assert!((score - 15.0).abs() < 0.01);
}
#[test]
fn reply_count_5_replies_half() {
let score = reply_count_score(5, 15.0);
assert!((score - 11.25).abs() < 0.01);
}
#[test]
fn reply_count_20_replies_zero() {
let score = reply_count_score(20, 15.0);
assert!((score - 0.0).abs() < 0.01);
}
#[test]
fn reply_count_50_replies_still_zero() {
let score = reply_count_score(50, 15.0);
assert!((score - 0.0).abs() < 0.01);
}
#[test]
fn targeted_follower_zero() {
assert_eq!(targeted_follower_score(0, 15.0), 0.0);
}
#[test]
fn targeted_follower_50_low() {
let score = targeted_follower_score(50, 15.0);
assert!(score > 0.0 && score < 7.5);
}
#[test]
fn targeted_follower_1000_sweet_spot() {
let score = targeted_follower_score(1000, 15.0);
assert!((score - 15.0).abs() < 0.1);
}
#[test]
fn targeted_follower_5000_still_sweet_spot() {
let score = targeted_follower_score(5000, 15.0);
assert!((score - 15.0).abs() < 0.01);
}
#[test]
fn targeted_follower_100k_drops() {
let score_1k = targeted_follower_score(1000, 15.0);
let score_100k = targeted_follower_score(100_000, 15.0);
assert!(score_1k > score_100k);
}
#[test]
fn targeted_follower_500k_floor() {
let score = targeted_follower_score(500_000, 15.0);
assert!((score - 3.75).abs() < 0.01);
}
#[test]
fn content_type_text_only_max() {
assert!((content_type_score(false, false, 10.0) - 10.0).abs() < 0.01);
}
#[test]
fn content_type_with_media_zero() {
assert!((content_type_score(true, false, 10.0) - 0.0).abs() < 0.01);
}
#[test]
fn content_type_quote_tweet_zero() {
assert!((content_type_score(false, true, 10.0) - 0.0).abs() < 0.01);
}
#[test]
fn content_type_media_and_quote_zero() {
assert!((content_type_score(true, true, 10.0) - 0.0).abs() < 0.01);
}
#[test]
fn keyword_relevance_clamped() {
let keywords = vec!["rust".to_string()];
let score = keyword_relevance("rust", &keywords, 25.0);
assert!(score <= 25.0);
}
#[test]
fn keyword_relevance_all_multi_word() {
let keywords = vec!["rust programming".to_string(), "cli tools".to_string()];
let score = keyword_relevance("rust programming and cli tools are great", &keywords, 30.0);
assert!((score - 30.0).abs() < 0.01);
}
#[test]
fn keyword_relevance_partial_multi_word() {
let keywords = vec![
"rust programming".to_string(),
"python scripting".to_string(),
];
let score = keyword_relevance("rust programming is great", &keywords, 40.0);
assert!((score - 20.0).abs() < 0.01); }
#[test]
fn follower_score_1() {
let score = follower_score(1, 20.0);
assert!((score - 0.0).abs() < 0.01);
}
#[test]
fn follower_score_10() {
let score = follower_score(10, 20.0);
assert!((score - 4.0).abs() < 0.1);
}
#[test]
fn recency_exactly_5_minutes() {
let now = Utc::now();
let created = (now - Duration::minutes(5)).to_rfc3339();
let score = recency_score_at(&created, 15.0, now);
assert!((score - 15.0).abs() < 0.5);
}
#[test]
fn recency_exactly_30_minutes() {
let now = Utc::now();
let created = (now - Duration::minutes(30)).to_rfc3339();
let score = recency_score_at(&created, 15.0, now);
let expected = 0.8 * 15.0;
assert!((score - expected).abs() < 0.5);
}
#[test]
fn recency_exactly_60_minutes() {
let now = Utc::now();
let created = (now - Duration::minutes(60)).to_rfc3339();
let score = recency_score_at(&created, 15.0, now);
let expected = 0.5 * 15.0;
assert!((score - expected).abs() < 0.5);
}
#[test]
fn recency_exactly_6_hours() {
let now = Utc::now();
let created = (now - Duration::hours(6)).to_rfc3339();
let score = recency_score_at(&created, 15.0, now);
let expected = 0.25 * 15.0;
assert!((score - expected).abs() < 0.5);
}
#[test]
fn recency_score_convenience_wrapper() {
let now = Utc::now();
let created = (now - Duration::minutes(2)).to_rfc3339();
let score = recency_score(&created, 10.0);
assert!(score > 8.0);
}
#[test]
fn reply_count_10_replies() {
let score = reply_count_score(10, 15.0);
assert!((score - 7.5).abs() < 0.01);
}
#[test]
fn reply_count_19_replies() {
let score = reply_count_score(19, 15.0);
assert!((score - 0.75).abs() < 0.01);
}
#[test]
fn reply_count_1_reply() {
let score = reply_count_score(1, 15.0);
assert!((score - 14.25).abs() < 0.01);
}
#[test]
fn targeted_follower_100() {
let score = targeted_follower_score(100, 15.0);
assert!((score - 7.5).abs() < 0.1);
}
#[test]
fn targeted_follower_500() {
let score = targeted_follower_score(500, 15.0);
let expected = 0.722 * 15.0;
assert!((score - expected as f32).abs() < 0.2);
}
#[test]
fn targeted_follower_10000() {
let score = targeted_follower_score(10_000, 15.0);
assert!((score - 15.0).abs() < 0.01);
}
#[test]
fn targeted_follower_50000() {
let score = targeted_follower_score(50_000, 15.0);
let expected = 0.667 * 15.0;
assert!((score - expected as f32).abs() < 0.2);
}
#[test]
fn targeted_follower_1_million() {
let score = targeted_follower_score(1_000_000, 15.0);
assert!((score - 3.75).abs() < 0.01);
}
#[test]
fn content_type_zero_max_score() {
assert!((content_type_score(false, false, 0.0) - 0.0).abs() < 0.01);
}
#[test]
fn engagement_mixed_metrics() {
let score = engagement_rate(20, 10, 5, 1000, 25.0);
assert!((score - 17.5).abs() < 0.1);
}
#[test]
fn engagement_1_like_1_follower() {
let score = engagement_rate(1, 0, 0, 1, 25.0);
assert!((score - 25.0).abs() < 0.01);
}