use church_core::{ models::*, cache::MemoryCache, ChurchCoreConfig, ChurchApiClient, }; use chrono::{DateTime, Utc, NaiveDate}; use std::time::Duration; use tokio_test; #[cfg(test)] mod model_tests { use super::*; use pretty_assertions::assert_eq; #[test] fn test_event_creation() { let event = Event { id: "test-id".to_string(), title: "Bible Study".to_string(), description: "Weekly study".to_string(), start_time: Utc::now(), end_time: Utc::now() + chrono::Duration::hours(2), location: "Fellowship Hall".to_string(), location_url: Some("https://maps.google.com".to_string()), image: None, thumbnail: None, category: EventCategory::Education, is_featured: true, recurring_type: Some(RecurringType::Weekly), tags: Some(vec!["bible".to_string(), "study".to_string()]), contact_email: Some("contact@church.org".to_string()), contact_phone: None, registration_url: None, max_attendees: Some(30), current_attendees: Some(15), created_at: Utc::now(), updated_at: Utc::now(), }; assert_eq!(event.title, "Bible Study"); assert_eq!(event.category, EventCategory::Education); assert!(event.is_featured); assert_eq!(event.spots_remaining(), Some(15)); assert!(!event.is_full()); } #[test] fn test_event_time_methods() { let now = Utc::now(); let past_time = now - chrono::Duration::hours(1); let future_time = now + chrono::Duration::hours(1); // Test upcoming event let upcoming_event = Event { id: "upcoming".to_string(), title: "Future Event".to_string(), description: "".to_string(), start_time: future_time, end_time: future_time + chrono::Duration::hours(1), location: "".to_string(), location_url: None, image: None, thumbnail: None, category: EventCategory::Service, is_featured: false, recurring_type: None, tags: None, contact_email: None, contact_phone: None, registration_url: None, max_attendees: None, current_attendees: None, created_at: now, updated_at: now, }; // Test duration calculation assert_eq!(upcoming_event.duration_minutes(), 60); // Test past event let past_event = Event { id: "past".to_string(), title: "Past Event".to_string(), description: "".to_string(), start_time: past_time - chrono::Duration::hours(1), end_time: past_time, location: "".to_string(), location_url: None, image: None, thumbnail: None, category: EventCategory::Service, is_featured: false, recurring_type: None, tags: None, contact_email: None, contact_phone: None, registration_url: None, max_attendees: None, current_attendees: None, created_at: past_time, updated_at: past_time, }; // Test duration calculation for past event assert_eq!(past_event.duration_minutes(), 60); } #[test] fn test_bulletin_creation() { let bulletin = Bulletin { id: "bulletin-1".to_string(), title: "Weekly Bulletin".to_string(), date: NaiveDate::from_ymd_opt(2024, 1, 6).unwrap(), sabbath_school: "9:30 AM".to_string(), divine_worship: "11:00 AM".to_string(), scripture_reading: "John 3:16".to_string(), sunset: "5:45 PM".to_string(), pdf_path: Some("/bulletins/2024-01-06.pdf".to_string()), cover_image: Some("/images/cover.jpg".to_string()), is_active: true, announcements: Some(vec![ Announcement { id: Some("ann-1".to_string()), title: "Potluck Dinner".to_string(), content: "Join us for fellowship".to_string(), category: Some(AnnouncementCategory::Social), is_urgent: false, contact_info: None, expires_at: Some(Utc::now() + chrono::Duration::days(7)), } ]), hymns: Some(vec![ BulletinHymn { number: 15, title: "Amazing Grace".to_string(), category: Some(HymnCategory::Opening), verses: Some(vec![1, 2, 4]), } ]), special_music: Some("Choir Special".to_string()), offering_type: Some("Local Church Budget".to_string()), sermon_title: Some("Walking in Faith".to_string()), speaker: Some("Pastor Smith".to_string()), liturgy: None, created_at: Utc::now(), updated_at: Utc::now(), }; assert_eq!(bulletin.title, "Weekly Bulletin"); assert!(bulletin.has_pdf()); assert!(bulletin.has_cover_image()); assert_eq!(bulletin.active_announcements().len(), 1); assert_eq!(bulletin.urgent_announcements().len(), 0); } #[test] fn test_contact_form_creation() { let contact = ContactForm::new( "John Doe".to_string(), "john@example.com".to_string(), "Prayer Request".to_string(), "Please pray for my family.".to_string(), ) .with_category(ContactCategory::PrayerRequest) .with_phone("555-123-4567".to_string()) .mark_urgent(); assert_eq!(contact.name, "John Doe"); assert_eq!(contact.email, "john@example.com"); assert!(contact.is_urgent()); assert!(!contact.is_visitor()); assert_eq!(contact.category, Some(ContactCategory::PrayerRequest)); } #[test] fn test_sermon_search() { let sermon = Sermon { id: "sermon-1".to_string(), title: "The Power of Prayer".to_string(), speaker: "Pastor Johnson".to_string(), description: "Learning to pray effectively".to_string(), date: Utc::now() - chrono::Duration::days(7), scripture_reference: "Matthew 6:9-13".to_string(), series: Some("Prayer Series".to_string()), duration_seconds: Some(2400), // 40 minutes media_url: Some("https://media.church.org/sermon1.mp4".to_string()), audio_url: Some("https://media.church.org/sermon1.mp3".to_string()), video_url: Some("https://video.church.org/sermon1.mp4".to_string()), transcript: Some("Today we explore...".to_string()), thumbnail: Some("https://media.church.org/thumb1.jpg".to_string()), tags: Some(vec!["prayer".to_string(), "spiritual".to_string()]), category: SermonCategory::Regular, is_featured: true, view_count: 150, download_count: 45, created_at: Utc::now() - chrono::Duration::days(8), updated_at: Utc::now() - chrono::Duration::days(7), }; assert_eq!(sermon.duration_formatted(), "40m"); assert!(sermon.has_media()); assert!(sermon.has_audio()); assert!(sermon.has_video()); assert!(sermon.has_transcript()); assert!(sermon.is_popular()); // Test search matching let search = SermonSearch::new() .with_query("prayer".to_string()) .with_speaker("Johnson".to_string()) .featured_only(); assert!(sermon.matches_search(&search)); let non_matching_search = SermonSearch::new() .with_query("baptism".to_string()); assert!(!sermon.matches_search(&non_matching_search)); } #[test] fn test_auth_token() { let token = AuthToken { token: "abc123".to_string(), token_type: "Bearer".to_string(), expires_at: Utc::now() + chrono::Duration::hours(1), user_id: Some("user-1".to_string()), user_name: Some("John Doe".to_string()), user_email: Some("john@church.org".to_string()), permissions: vec!["events.read".to_string(), "bulletins.read".to_string()], }; assert!(token.is_valid()); assert!(!token.is_expired()); assert!(token.has_permission("events.read")); assert!(!token.has_permission("admin.write")); assert!(token.has_any_permission(&["events.read", "admin.write"])); assert!(!token.has_all_permissions(&["events.read", "admin.write"])); } #[test] fn test_user_roles() { let admin_user = AuthUser { id: "admin-1".to_string(), email: "admin@church.org".to_string(), name: "Admin User".to_string(), username: Some("admin".to_string()), avatar: None, verified: true, role: UserRole::Admin, permissions: vec!["admin.*".to_string()], created_at: Utc::now(), updated_at: Utc::now(), last_login: Some(Utc::now()), }; assert!(admin_user.is_admin()); assert!(admin_user.is_leadership()); assert!(admin_user.can_edit_content()); assert!(admin_user.can_moderate()); assert_eq!(admin_user.display_name(), "Admin User"); let member_user = AuthUser { id: "member-1".to_string(), email: "member@church.org".to_string(), name: "".to_string(), username: Some("member123".to_string()), avatar: None, verified: true, role: UserRole::Member, permissions: vec!["events.read".to_string()], created_at: Utc::now(), updated_at: Utc::now(), last_login: Some(Utc::now()), }; assert!(!member_user.is_admin()); assert!(!member_user.is_leadership()); assert!(!member_user.can_edit_content()); assert!(!member_user.can_moderate()); assert!(member_user.is_member()); assert_eq!(member_user.display_name(), "member123"); } #[test] fn test_api_response_models() { let response: ApiResponse = ApiResponse { success: true, data: Some("test data".to_string()), message: Some("Success".to_string()), error: None, }; assert!(response.success); assert_eq!(response.data, Some("test data".to_string())); let error_response: ApiResponse = ApiResponse { success: false, data: None, message: None, error: Some("Not found".to_string()), }; assert!(!error_response.success); assert_eq!(error_response.data, None); } #[test] fn test_pagination_params() { let params = PaginationParams::new() .with_page(2) .with_per_page(25) .with_sort("created_at".to_string()) .with_filter("active=true".to_string()); assert_eq!(params.page, Some(2)); assert_eq!(params.per_page, Some(25)); assert_eq!(params.sort, Some("created_at".to_string())); assert_eq!(params.filter, Some("active=true".to_string())); } } #[cfg(test)] mod cache_tests { use super::*; use tokio_test; #[tokio::test] async fn test_memory_cache_basic_operations() { let cache = MemoryCache::new(100); let ttl = Duration::from_secs(60); // Test set and get cache.set("key1", &"value1", ttl).await; let result: Option = cache.get("key1").await; assert_eq!(result, Some("value1".to_string())); // Test get non-existent key let result: Option = cache.get("nonexistent").await; assert_eq!(result, None); // Test remove cache.remove("key1").await; let result: Option = cache.get("key1").await; assert_eq!(result, None); } #[tokio::test] async fn test_memory_cache_ttl_expiration() { let cache = MemoryCache::new(100); let short_ttl = Duration::from_millis(50); // Set value with short TTL cache.set("expire_key", &"expire_value", short_ttl).await; // Should be available immediately let result: Option = cache.get("expire_key").await; assert_eq!(result, Some("expire_value".to_string())); // Wait for expiration tokio::time::sleep(Duration::from_millis(100)).await; // Should be expired let result: Option = cache.get("expire_key").await; assert_eq!(result, None); } #[tokio::test] async fn test_memory_cache_clear() { let cache = MemoryCache::new(100); let ttl = Duration::from_secs(60); // Set multiple values cache.set("key1", &"value1", ttl).await; cache.set("key2", &"value2", ttl).await; cache.set("key3", &"value3", ttl).await; assert_eq!(cache.len().await, 3); // Clear cache cache.clear().await; assert_eq!(cache.len().await, 0); // Verify values are gone let result: Option = cache.get("key1").await; assert_eq!(result, None); } #[tokio::test] async fn test_memory_cache_invalidate_prefix() { let cache = MemoryCache::new(100); let ttl = Duration::from_secs(60); // Set values with different prefixes cache.set("user:1", &"user1", ttl).await; cache.set("user:2", &"user2", ttl).await; cache.set("event:1", &"event1", ttl).await; cache.set("event:2", &"event2", ttl).await; assert_eq!(cache.len().await, 4); // Invalidate user prefix cache.invalidate_prefix("user:").await; assert_eq!(cache.len().await, 2); // Verify user keys are gone but event keys remain let result: Option = cache.get("user:1").await; assert_eq!(result, None); let result: Option = cache.get("event:1").await; assert_eq!(result, Some("event1".to_string())); } #[tokio::test] async fn test_memory_cache_complex_types() { let cache = MemoryCache::new(100); let ttl = Duration::from_secs(60); let event = Event { id: "test-event".to_string(), title: "Test Event".to_string(), description: "A test event".to_string(), start_time: Utc::now(), end_time: Utc::now() + chrono::Duration::hours(1), location: "Test Location".to_string(), location_url: None, image: None, thumbnail: None, category: EventCategory::Service, is_featured: false, recurring_type: None, tags: None, contact_email: None, contact_phone: None, registration_url: None, max_attendees: None, current_attendees: None, created_at: Utc::now(), updated_at: Utc::now(), }; // Cache complex object cache.set("event:test", &event, ttl).await; // Retrieve and verify let result: Option = cache.get("event:test").await; assert!(result.is_some()); let cached_event = result.unwrap(); assert_eq!(cached_event.id, "test-event"); assert_eq!(cached_event.title, "Test Event"); assert_eq!(cached_event.category, EventCategory::Service); } } #[cfg(test)] mod config_tests { use super::*; #[test] fn test_church_core_config() { let config = ChurchCoreConfig::new() .with_base_url("https://api.test.church") .with_cache_ttl(Duration::from_secs(600)) .with_timeout(Duration::from_secs(15)) .with_retry_attempts(5) .with_offline_mode(false) .with_max_cache_size(2000); assert_eq!(config.api_base_url, "https://api.test.church"); assert_eq!(config.cache_ttl, Duration::from_secs(600)); assert_eq!(config.timeout, Duration::from_secs(15)); assert_eq!(config.retry_attempts, 5); assert!(!config.enable_offline_mode); assert_eq!(config.max_cache_size, 2000); } #[test] fn test_default_config() { let config = ChurchCoreConfig::default(); assert_eq!(config.api_base_url, "https://api.rockvilletollandsda.church/api"); assert_eq!(config.cache_ttl, Duration::from_secs(300)); assert_eq!(config.timeout, Duration::from_secs(10)); assert_eq!(config.retry_attempts, 3); assert!(config.enable_offline_mode); assert_eq!(config.max_cache_size, 1000); } }