test: add unit tests for all reviewed domain modules
Add 57 unit tests across auth (sessions, registration tokens, API tokens, username validation), coffee (roasters, cafes, brews, gear), listing (pagination, sort keys, page calculations), analytics (country stats), images (debug redaction), and entity type (roundtrip serialization).
This commit is contained in:
parent
bd4cb023fb
commit
f7b210f234
12 changed files with 732 additions and 0 deletions
|
|
@ -50,3 +50,36 @@ impl GeoStats {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn from_counts_empty() {
|
||||
let stats = GeoStats::from_counts(vec![]);
|
||||
assert!(stats.entries.is_empty());
|
||||
assert_eq!(stats.total_countries, 0);
|
||||
assert_eq!(stats.max_count, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_counts_known_country() {
|
||||
let stats = GeoStats::from_counts(vec![("United States".to_string(), 5)]);
|
||||
assert_eq!(stats.entries.len(), 1);
|
||||
assert_eq!(stats.entries[0].iso_code, "US");
|
||||
assert!(!stats.entries[0].flag_emoji.is_empty());
|
||||
assert_eq!(stats.entries[0].count, 5);
|
||||
assert_eq!(stats.total_countries, 1);
|
||||
assert_eq!(stats.max_count, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_counts_unknown_country() {
|
||||
let stats = GeoStats::from_counts(vec![("Atlantis".to_string(), 3)]);
|
||||
assert_eq!(stats.entries.len(), 1);
|
||||
assert_eq!(stats.entries[0].iso_code, "");
|
||||
assert_eq!(stats.entries[0].country_name, "Atlantis");
|
||||
assert_eq!(stats.entries[0].count, 3);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,3 +49,75 @@ impl NewRegistrationToken {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn token_valid() {
|
||||
let now = Utc::now();
|
||||
let token = RegistrationToken {
|
||||
id: RegistrationTokenId::new(1),
|
||||
token_hash: "hash".to_string(),
|
||||
created_at: now,
|
||||
expires_at: now + Duration::hours(1),
|
||||
used_at: None,
|
||||
used_by_user_id: None,
|
||||
};
|
||||
assert!(token.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_expired() {
|
||||
let now = Utc::now();
|
||||
let token = RegistrationToken {
|
||||
id: RegistrationTokenId::new(1),
|
||||
token_hash: "hash".to_string(),
|
||||
created_at: now - Duration::hours(2),
|
||||
expires_at: now - Duration::hours(1),
|
||||
used_at: None,
|
||||
used_by_user_id: None,
|
||||
};
|
||||
assert!(!token.is_valid());
|
||||
assert!(token.is_expired());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_used() {
|
||||
let now = Utc::now();
|
||||
let token = RegistrationToken {
|
||||
id: RegistrationTokenId::new(1),
|
||||
token_hash: "hash".to_string(),
|
||||
created_at: now,
|
||||
expires_at: now + Duration::hours(1),
|
||||
used_at: Some(now),
|
||||
used_by_user_id: Some(UserId::new(1)),
|
||||
};
|
||||
assert!(!token.is_valid());
|
||||
assert!(token.is_used());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_expired_and_used() {
|
||||
let now = Utc::now();
|
||||
let token = RegistrationToken {
|
||||
id: RegistrationTokenId::new(1),
|
||||
token_hash: "hash".to_string(),
|
||||
created_at: now - Duration::hours(2),
|
||||
expires_at: now - Duration::hours(1),
|
||||
used_at: Some(now - Duration::minutes(30)),
|
||||
used_by_user_id: Some(UserId::new(1)),
|
||||
};
|
||||
assert!(!token.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_token_clamps_excessive_duration() {
|
||||
let now = Utc::now();
|
||||
let excessive_expires = now + Duration::days(30);
|
||||
let token = NewRegistrationToken::new("hash".to_string(), now, excessive_expires);
|
||||
let expected_max = now + MAX_TOKEN_DURATION;
|
||||
assert_eq!(token.expires_at, expected_max);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,3 +75,43 @@ impl NewSession {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn session_not_expired() {
|
||||
let now = Utc::now();
|
||||
let session = Session::new(
|
||||
SessionId::new(1),
|
||||
UserId::new(1),
|
||||
"hash".to_string(),
|
||||
now,
|
||||
now + Duration::hours(1),
|
||||
);
|
||||
assert!(!session.is_expired());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_expired() {
|
||||
let now = Utc::now();
|
||||
let session = Session::new(
|
||||
SessionId::new(1),
|
||||
UserId::new(1),
|
||||
"hash".to_string(),
|
||||
now - Duration::hours(2),
|
||||
now - Duration::hours(1),
|
||||
);
|
||||
assert!(session.is_expired());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_session_clamps_excessive_duration() {
|
||||
let now = Utc::now();
|
||||
let excessive_expires = now + Duration::days(60);
|
||||
let session = NewSession::new(UserId::new(1), "hash".to_string(), now, excessive_expires);
|
||||
let expected_max = now + MAX_SESSION_DURATION;
|
||||
assert_eq!(session.expires_at, expected_max);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,3 +71,40 @@ impl NewToken {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn token_active_when_not_revoked() {
|
||||
let now = Utc::now();
|
||||
let token = Token::new(
|
||||
TokenId::new(1),
|
||||
UserId::new(1),
|
||||
"hash".to_string(),
|
||||
"my-token".to_string(),
|
||||
now,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert!(token.is_active());
|
||||
assert!(!token.is_revoked());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_revoked() {
|
||||
let now = Utc::now();
|
||||
let token = Token::new(
|
||||
TokenId::new(1),
|
||||
UserId::new(1),
|
||||
"hash".to_string(),
|
||||
"my-token".to_string(),
|
||||
now,
|
||||
None,
|
||||
Some(now),
|
||||
);
|
||||
assert!(token.is_revoked());
|
||||
assert!(!token.is_active());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,3 +55,48 @@ impl NewUser {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn valid_username_alphanumeric() {
|
||||
assert!(is_valid_username("abc123"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_username_with_underscores() {
|
||||
assert!(is_valid_username("my_user"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_username_with_hyphens() {
|
||||
assert!(is_valid_username("my-user"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn username_too_short() {
|
||||
assert!(!is_valid_username("ab"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn username_too_long() {
|
||||
assert!(!is_valid_username(&"a".repeat(33)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn username_with_spaces() {
|
||||
assert!(!is_valid_username("my user"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn username_min_length() {
|
||||
assert!(is_valid_username("abc"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn username_max_length() {
|
||||
assert!(is_valid_username(&"a".repeat(32)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -278,3 +278,50 @@ define_sort_key!(pub BrewSortKey {
|
|||
CoffeeWeight("coffee-weight", Desc),
|
||||
WaterVolume("water-volume", Desc),
|
||||
});
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn quick_note_roundtrip_form_value() {
|
||||
for &variant in QuickNote::all() {
|
||||
assert_eq!(
|
||||
QuickNote::from_str_value(variant.form_value()),
|
||||
Some(variant)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quick_note_roundtrip_label() {
|
||||
for &variant in QuickNote::all() {
|
||||
assert_eq!(QuickNote::from_str_value(variant.label()), Some(variant));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quick_note_unknown_returns_none() {
|
||||
assert_eq!(QuickNote::from_str_value("invalid"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quick_note_from_str_trait() {
|
||||
assert_eq!("good".parse::<QuickNote>(), Ok(QuickNote::Good));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_brew_time_minutes_seconds() {
|
||||
assert_eq!(format_brew_time(150), "2:30");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_brew_time_zero() {
|
||||
assert_eq!(format_brew_time(0), "0:00");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_brew_time_under_minute() {
|
||||
assert_eq!(format_brew_time(45), "0:45");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,3 +102,69 @@ define_sort_key!(pub CafeSortKey {
|
|||
City("city", Asc),
|
||||
Country("country", Asc),
|
||||
});
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn normalize_trims_fields() {
|
||||
let cafe = NewCafe {
|
||||
name: " Test Cafe ".to_string(),
|
||||
city: " Portland ".to_string(),
|
||||
country: " US ".to_string(),
|
||||
latitude: 45.5,
|
||||
longitude: -122.6,
|
||||
website: None,
|
||||
created_at: None,
|
||||
};
|
||||
let normalized = cafe.normalize();
|
||||
assert_eq!(normalized.name, "Test Cafe");
|
||||
assert_eq!(normalized.city, "Portland");
|
||||
assert_eq!(normalized.country, "US");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_filters_bad_website() {
|
||||
let cafe = NewCafe {
|
||||
name: "Test Cafe".to_string(),
|
||||
city: "Portland".to_string(),
|
||||
country: "US".to_string(),
|
||||
latitude: 45.5,
|
||||
longitude: -122.6,
|
||||
website: Some("javascript:alert(1)".to_string()),
|
||||
created_at: None,
|
||||
};
|
||||
let normalized = cafe.normalize();
|
||||
assert_eq!(normalized.website, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_keeps_valid_website() {
|
||||
let cafe = NewCafe {
|
||||
name: "Test Cafe".to_string(),
|
||||
city: "Portland".to_string(),
|
||||
country: "US".to_string(),
|
||||
latitude: 45.5,
|
||||
longitude: -122.6,
|
||||
website: Some("https://testcafe.com".to_string()),
|
||||
created_at: None,
|
||||
};
|
||||
let normalized = cafe.normalize();
|
||||
assert_eq!(normalized.website, Some("https://testcafe.com".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slug_generation() {
|
||||
let cafe = NewCafe {
|
||||
name: "Test Cafe".to_string(),
|
||||
city: "Portland".to_string(),
|
||||
country: "US".to_string(),
|
||||
latitude: 45.5,
|
||||
longitude: -122.6,
|
||||
website: None,
|
||||
created_at: None,
|
||||
};
|
||||
assert_eq!(cafe.slug(), "test-cafe-portland");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,3 +129,23 @@ define_sort_key!(pub GearSortKey {
|
|||
Model("model", Asc),
|
||||
Category("category", Asc),
|
||||
});
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn gear_category_from_str_valid() {
|
||||
assert_eq!("grinder".parse::<GearCategory>(), Ok(GearCategory::Grinder));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gear_category_case_insensitive() {
|
||||
assert_eq!("BREWER".parse::<GearCategory>(), Ok(GearCategory::Brewer));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gear_category_invalid() {
|
||||
assert!("invalid".parse::<GearCategory>().is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,3 +106,111 @@ define_sort_key!(pub RoasterSortKey {
|
|||
Country("country", Asc),
|
||||
City("city", Asc),
|
||||
});
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn valid_http_scheme() {
|
||||
assert!(is_valid_url_scheme("http://example.com"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_https_scheme() {
|
||||
assert!(is_valid_url_scheme("https://example.com"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_javascript_scheme() {
|
||||
assert!(!is_valid_url_scheme("javascript:alert(1)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_data_scheme() {
|
||||
assert!(!is_valid_url_scheme("data:text/html,<h1>hi</h1>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn case_insensitive_http() {
|
||||
assert!(is_valid_url_scheme("HTTP://EXAMPLE.COM"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_ftp_scheme() {
|
||||
assert!(!is_valid_url_scheme("ftp://example.com"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_empty_string() {
|
||||
assert!(!is_valid_url_scheme(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trims_whitespace() {
|
||||
assert!(is_valid_url_scheme(" https://example.com "));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_trims_name() {
|
||||
let roaster = NewRoaster {
|
||||
name: " Test ".to_string(),
|
||||
country: "US".to_string(),
|
||||
city: Some("Portland".to_string()),
|
||||
homepage: Some("https://test.com".to_string()),
|
||||
created_at: None,
|
||||
};
|
||||
let normalized = roaster.normalize();
|
||||
assert_eq!(normalized.name, "Test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_filters_bad_scheme() {
|
||||
let roaster = NewRoaster {
|
||||
name: "Test".to_string(),
|
||||
country: "US".to_string(),
|
||||
city: Some("Portland".to_string()),
|
||||
homepage: Some("javascript:alert(1)".to_string()),
|
||||
created_at: None,
|
||||
};
|
||||
let normalized = roaster.normalize();
|
||||
assert_eq!(normalized.homepage, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_keeps_valid_homepage() {
|
||||
let roaster = NewRoaster {
|
||||
name: "Test".to_string(),
|
||||
country: "US".to_string(),
|
||||
city: Some("Portland".to_string()),
|
||||
homepage: Some("https://test.com".to_string()),
|
||||
created_at: None,
|
||||
};
|
||||
let normalized = roaster.normalize();
|
||||
assert_eq!(normalized.homepage, Some("https://test.com".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slug_with_city() {
|
||||
let roaster = NewRoaster {
|
||||
name: "Test".to_string(),
|
||||
country: "US".to_string(),
|
||||
city: Some("Portland".to_string()),
|
||||
homepage: Some("https://test.com".to_string()),
|
||||
created_at: None,
|
||||
};
|
||||
assert_eq!(roaster.slug(), "test-portland");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slug_without_city() {
|
||||
let roaster = NewRoaster {
|
||||
name: "Test".to_string(),
|
||||
country: "US".to_string(),
|
||||
city: None,
|
||||
homepage: Some("https://test.com".to_string()),
|
||||
created_at: None,
|
||||
};
|
||||
assert_eq!(roaster.slug(), "test");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,3 +51,44 @@ impl FromStr for EntityType {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const ALL_VARIANTS: [EntityType; 7] = [
|
||||
EntityType::Roaster,
|
||||
EntityType::Roast,
|
||||
EntityType::Bag,
|
||||
EntityType::Brew,
|
||||
EntityType::Cup,
|
||||
EntityType::Cafe,
|
||||
EntityType::Gear,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn as_str_roundtrip() {
|
||||
for variant in ALL_VARIANTS {
|
||||
assert_eq!(variant.as_str().parse::<EntityType>(), Ok(variant));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_matches_as_str() {
|
||||
assert_eq!(format!("{}", EntityType::Brew), "brew");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str_unknown_is_err() {
|
||||
assert!("unknown".parse::<EntityType>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_roundtrip() {
|
||||
for variant in ALL_VARIANTS {
|
||||
let json = serde_json::to_string(&variant).unwrap();
|
||||
let deserialized: EntityType = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(deserialized, variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,3 +45,20 @@ impl fmt::Debug for ImageData {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn image_data_debug_redacts_some() {
|
||||
let data: ImageData = serde_json::from_str(r#""data:image/png;base64,abc""#).unwrap();
|
||||
assert_eq!(format!("{data:?}"), "Some(<image>)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_data_debug_shows_none() {
|
||||
let data = ImageData::default();
|
||||
assert_eq!(format!("{data:?}"), "None");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -288,3 +288,209 @@ impl<T> Page<T> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
crate::define_sort_key!(TestSortKey {
|
||||
#[default]
|
||||
CreatedAt("created-at", Desc),
|
||||
Name("name", Asc),
|
||||
});
|
||||
|
||||
// --- PageSize tests ---
|
||||
|
||||
#[test]
|
||||
fn zero_becomes_all() {
|
||||
assert_eq!(PageSize::limited(0), PageSize::All);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn limited_returns_value() {
|
||||
assert_eq!(PageSize::limited(10).as_option(), Some(10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_has_no_option() {
|
||||
assert_eq!(PageSize::All.as_option(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_is_all() {
|
||||
assert!(PageSize::All.is_all());
|
||||
}
|
||||
|
||||
// --- ListRequest::new tests ---
|
||||
|
||||
#[test]
|
||||
fn page_min_is_1() {
|
||||
let req = ListRequest::new(
|
||||
0,
|
||||
PageSize::Limited(10),
|
||||
TestSortKey::CreatedAt,
|
||||
SortDirection::Desc,
|
||||
);
|
||||
assert_eq!(req.page, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn size_clamped_to_max() {
|
||||
let req = ListRequest::new(
|
||||
1,
|
||||
PageSize::Limited(51),
|
||||
TestSortKey::CreatedAt,
|
||||
SortDirection::Desc,
|
||||
);
|
||||
assert_eq!(req.page_size, PageSize::Limited(MAX_PAGE_SIZE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_size_becomes_all() {
|
||||
let req = ListRequest::new(
|
||||
1,
|
||||
PageSize::Limited(0),
|
||||
TestSortKey::CreatedAt,
|
||||
SortDirection::Desc,
|
||||
);
|
||||
assert_eq!(req.page_size, PageSize::All);
|
||||
}
|
||||
|
||||
// --- ListRequest::default_query tests ---
|
||||
|
||||
#[test]
|
||||
fn default_query_has_page_1() {
|
||||
let req = ListRequest::<TestSortKey>::default_query();
|
||||
assert_eq!(req.page, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_query_has_default_page_size() {
|
||||
let req = ListRequest::<TestSortKey>::default_query();
|
||||
assert_eq!(req.page_size, PageSize::Limited(DEFAULT_PAGE_SIZE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_query_has_default_sort_key() {
|
||||
let req = ListRequest::<TestSortKey>::default_query();
|
||||
assert_eq!(req.sort_key, TestSortKey::CreatedAt);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_query_has_default_sort_direction() {
|
||||
let req = ListRequest::<TestSortKey>::default_query();
|
||||
assert_eq!(req.sort_direction, SortDirection::Desc);
|
||||
}
|
||||
|
||||
// --- with_sort tests ---
|
||||
|
||||
#[test]
|
||||
fn with_sort_same_key_toggles_direction() {
|
||||
let req = ListRequest::new(
|
||||
1,
|
||||
PageSize::Limited(10),
|
||||
TestSortKey::CreatedAt,
|
||||
SortDirection::Desc,
|
||||
);
|
||||
let toggled = req.with_sort(TestSortKey::CreatedAt);
|
||||
assert_eq!(toggled.sort_direction, SortDirection::Asc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_sort_new_key_uses_default_direction() {
|
||||
let req = ListRequest::new(
|
||||
1,
|
||||
PageSize::Limited(10),
|
||||
TestSortKey::CreatedAt,
|
||||
SortDirection::Desc,
|
||||
);
|
||||
let switched = req.with_sort(TestSortKey::Name);
|
||||
assert_eq!(switched.sort_direction, SortDirection::Asc);
|
||||
}
|
||||
|
||||
// --- ensure_page_within tests ---
|
||||
|
||||
#[test]
|
||||
fn clamps_page_beyond_total() {
|
||||
let req = ListRequest::new(
|
||||
10,
|
||||
PageSize::Limited(10),
|
||||
TestSortKey::CreatedAt,
|
||||
SortDirection::Desc,
|
||||
);
|
||||
let clamped = req.ensure_page_within(25);
|
||||
assert_eq!(clamped.page, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_total_returns_page_1() {
|
||||
let req = ListRequest::new(
|
||||
5,
|
||||
PageSize::Limited(10),
|
||||
TestSortKey::CreatedAt,
|
||||
SortDirection::Desc,
|
||||
);
|
||||
let clamped = req.ensure_page_within(0);
|
||||
assert_eq!(clamped.page, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_mode_returns_page_1() {
|
||||
let req = ListRequest::new(
|
||||
3,
|
||||
PageSize::All,
|
||||
TestSortKey::CreatedAt,
|
||||
SortDirection::Desc,
|
||||
);
|
||||
let clamped = req.ensure_page_within(100);
|
||||
assert_eq!(clamped.page, 1);
|
||||
}
|
||||
|
||||
// --- Page tests ---
|
||||
|
||||
#[test]
|
||||
fn total_pages_calculation() {
|
||||
let page: Page<()> = Page::new(vec![], 1, 10, 25, false);
|
||||
assert_eq!(page.total_pages(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn total_pages_zero_is_1() {
|
||||
let page: Page<()> = Page::new(vec![], 1, 10, 0, false);
|
||||
assert_eq!(page.total_pages(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_previous_false_on_page_1() {
|
||||
let page: Page<()> = Page::new(vec![], 1, 10, 30, false);
|
||||
assert!(!page.has_previous());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_next_false_on_last_page() {
|
||||
let page: Page<()> = Page::new(vec![], 3, 10, 30, false);
|
||||
assert!(!page.has_next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn start_end_index() {
|
||||
let page: Page<i32> = Page::new(vec![1, 2, 3], 2, 10, 30, false);
|
||||
assert_eq!(page.start_index(), 11);
|
||||
assert_eq!(page.end_index(), 13);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn start_end_index_zero_total() {
|
||||
let page: Page<()> = Page::new(vec![], 1, 10, 0, false);
|
||||
assert_eq!(page.start_index(), 0);
|
||||
assert_eq!(page.end_index(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn showing_all_single_page() {
|
||||
let page: Page<i32> = Page::new(vec![1, 2, 3], 1, 3, 3, true);
|
||||
assert_eq!(page.total_pages(), 1);
|
||||
assert!(!page.has_previous());
|
||||
assert!(!page.has_next());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue