use crate::prelude::*;
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct User {
pub username: String,
pub avatar: String,
pub is_friend: bool,
pub profile_text: String,
pub bb_profile_text: Option<String>,
pub stats: Stats,
pub ranks: Ranks,
pub personal: Personal,
pub community: Community,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Stats {
pub joined_date: String,
pub last_access: String,
pub uploaded: u64,
pub downloaded: u64,
pub ratio: f32,
pub required_ratio: f32,
}
#[derive(Clone, Debug, Deserialize)]
pub struct Ranks {
pub uploaded: f32,
pub downloaded: f32,
pub uploads: f32,
pub requests: f32,
pub bounty: f32,
pub posts: f32,
pub artists: f32,
pub overall: f32,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Personal {
pub class: String,
pub paranoia: u8,
pub paranoia_text: String,
pub donor: bool,
pub warned: bool,
pub enabled: bool,
pub passkey: String,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Community {
pub posts: u32,
pub torrent_comments: u32,
pub collages_started: u32,
pub collages_contrib: u32,
pub requests_filled: u32,
pub requests_voted: u32,
pub perfect_flacs: u32,
pub uploaded: u32,
pub groups: u32,
pub seeding: u32,
pub leeching: u32,
pub snatched: u32,
pub invited: u32,
}
#[cfg(feature = "mock")]
impl User {
#[must_use]
pub fn mock() -> Self {
Self {
username: "testuser".to_owned(),
avatar: "https://example.com/avatar.png".to_owned(),
is_friend: false,
profile_text: "Test profile".to_owned(),
bb_profile_text: None,
stats: Stats::mock(),
ranks: Ranks::mock(),
personal: Personal::mock(),
community: Community::mock(),
}
}
}
#[cfg(feature = "mock")]
impl Stats {
#[must_use]
pub fn mock() -> Self {
Self {
joined_date: "2020-01-01 00:00:00".to_owned(),
last_access: "2024-01-01 00:00:00".to_owned(),
uploaded: 1_000_000_000,
downloaded: 500_000_000,
ratio: 2.0,
required_ratio: 0.5,
}
}
}
#[cfg(feature = "mock")]
impl Ranks {
#[must_use]
pub fn mock() -> Self {
Self {
uploaded: 90.0,
downloaded: 50.0,
uploads: 80.0,
requests: 10.0,
bounty: 5.0,
posts: 20.0,
artists: 15.0,
overall: 60.0,
}
}
}
#[cfg(feature = "mock")]
impl Personal {
#[must_use]
pub fn mock() -> Self {
Self {
class: "Member".to_owned(),
paranoia: 0,
paranoia_text: "Off".to_owned(),
donor: false,
warned: false,
enabled: true,
passkey: "testpasskey".to_owned(),
}
}
}
#[cfg(feature = "mock")]
impl Community {
#[must_use]
pub fn mock() -> Self {
Self {
posts: 100,
torrent_comments: 50,
collages_started: 2,
collages_contrib: 5,
requests_filled: 10,
requests_voted: 20,
perfect_flacs: 30,
uploaded: 40,
groups: 35,
seeding: 100,
leeching: 2,
snatched: 200,
invited: 5,
}
}
}
#[cfg(test)]
#[expect(
clippy::float_cmp,
reason = "exact float equality is intentional in fixture tests"
)]
mod tests {
use super::*;
const OPS_RESPONSE: &str = include_str!("../tests/fixtures/user_response_ops.json");
const RED_RESPONSE: &str = include_str!("../tests/fixtures/user_response_red.json");
#[test]
fn deserialize_ops_user_response() {
let user: User = json_from_str(OPS_RESPONSE).expect("should deserialize");
assert!(user.bb_profile_text.is_none());
assert_eq!(user.username, "TestUser");
assert!(!user.is_friend);
assert!(user.profile_text.is_empty());
}
#[test]
fn deserialize_red_user_response() {
let user: User = json_from_str(RED_RESPONSE).expect("should deserialize");
assert!(user.bb_profile_text.is_some());
assert!(
user.bb_profile_text
.as_ref()
.expect("bb_profile_text should exist")
.contains("Developer")
);
assert_eq!(user.username, "TestUser");
assert!(!user.is_friend);
assert!(user.profile_text.contains("Developer"));
}
#[test]
fn deserialize_user_stats() {
let user: User = json_from_str(OPS_RESPONSE).expect("should deserialize");
assert_eq!(user.stats.joined_date, "2023-04-13 03:49:47");
assert_eq!(user.stats.uploaded, 1_207_152_087_233);
assert_eq!(user.stats.downloaded, 373_638_950_466);
assert!(user.stats.ratio > 3.0);
assert!(user.stats.required_ratio < 1.0);
}
#[test]
fn deserialize_user_ranks() {
let user: User = json_from_str(OPS_RESPONSE).expect("should deserialize");
assert_eq!(user.ranks.uploaded, 95.0);
assert_eq!(user.ranks.downloaded, 93.0);
assert_eq!(user.ranks.uploads, 94.0);
assert_eq!(user.ranks.overall, 36.0);
}
#[test]
fn deserialize_user_personal() {
let user: User = json_from_str(OPS_RESPONSE).expect("should deserialize");
assert_eq!(user.personal.class, "Torrent Master");
assert_eq!(user.personal.paranoia, 34);
assert_eq!(user.personal.paranoia_text, "Very high");
assert!(!user.personal.donor);
assert!(!user.personal.warned);
assert!(user.personal.enabled);
}
#[test]
fn deserialize_user_community() {
let user: User = json_from_str(OPS_RESPONSE).expect("should deserialize");
assert_eq!(user.community.posts, 9);
assert_eq!(user.community.perfect_flacs, 81);
assert_eq!(user.community.uploaded, 578);
assert_eq!(user.community.groups, 287);
assert_eq!(user.community.seeding, 1127);
assert_eq!(user.community.snatched, 982);
}
#[test]
fn deserialize_red_user_community() {
let user: User = json_from_str(RED_RESPONSE).expect("should deserialize");
assert_eq!(user.community.posts, 63);
assert_eq!(user.community.perfect_flacs, 246);
assert_eq!(user.community.seeding, 2660);
assert_eq!(user.community.snatched, 4789);
}
}