use module_runtime::{ AnalyticsGranularity, RuntimeAnalyticsDailyStatSnapshot, RuntimeTrackingScopeKind, aggregate_runtime_tracking_daily_stats, }; fn stat(event_key: &str, scope_id: &str, day_key: i64, count: u32) -> RuntimeAnalyticsDailyStatSnapshot { RuntimeAnalyticsDailyStatSnapshot { event_key: event_key.to_string(), scope_kind: RuntimeTrackingScopeKind::User, scope_id: scope_id.to_string(), day_key, count, } } #[test] fn aggregates_daily_stats_by_iso_week_bucket() { let buckets = aggregate_runtime_tracking_daily_stats( vec![ stat("daily_login", "user-1", 20_517, 1), // 2026-03-05 stat("daily_login", "user-1", 20_518, 2), // 2026-03-06 stat("daily_login", "user-1", 20_524, 3), // 2026-03-12 stat("daily_login", "user-2", 20_517, 9), ], "daily_login", RuntimeTrackingScopeKind::User, "user-1", AnalyticsGranularity::Week, ); assert_eq!(buckets.len(), 2); assert_eq!(buckets[0].bucket_key, "202610"); assert_eq!(buckets[0].bucket_start_date_key, 20_514); // 2026-03-02 assert_eq!(buckets[0].bucket_end_date_key, 20_520); // 2026-03-08 assert_eq!(buckets[0].value, 3); assert_eq!(buckets[1].bucket_key, "202611"); assert_eq!(buckets[1].bucket_start_date_key, 20_521); // 2026-03-09 assert_eq!(buckets[1].bucket_end_date_key, 20_527); // 2026-03-15 assert_eq!(buckets[1].value, 3); } #[test] fn aggregates_daily_stats_by_month_quarter_and_year_bucket() { let stats = vec![ stat("play", "user-1", 20_545, 2), // 2026-04-02 stat("play", "user-1", 20_573, 3), // 2026-04-30 stat("play", "user-1", 20_574, 5), // 2026-05-01 stat("play", "user-1", 20_818, 7), // 2026-12-31 ]; let month = aggregate_runtime_tracking_daily_stats( stats.clone(), "play", RuntimeTrackingScopeKind::User, "user-1", AnalyticsGranularity::Month, ); assert_eq!(month.iter().map(|bucket| (&bucket.bucket_key, bucket.value)).collect::>(), vec![(&"202604".to_string(), 5), (&"202605".to_string(), 5), (&"202612".to_string(), 7)]); assert_eq!(month[0].bucket_start_date_key, 20_544); assert_eq!(month[0].bucket_end_date_key, 20_573); let quarter = aggregate_runtime_tracking_daily_stats( stats.clone(), "play", RuntimeTrackingScopeKind::User, "user-1", AnalyticsGranularity::Quarter, ); assert_eq!(quarter.iter().map(|bucket| (&bucket.bucket_key, bucket.value)).collect::>(), vec![(&"20262".to_string(), 10), (&"20264".to_string(), 7)]); let year = aggregate_runtime_tracking_daily_stats( stats, "play", RuntimeTrackingScopeKind::User, "user-1", AnalyticsGranularity::Year, ); assert_eq!(year.len(), 1); assert_eq!(year[0].bucket_key, "2026"); assert_eq!(year[0].value, 17); } #[test] fn day_granularity_keeps_each_day_bucket_and_filters_scope() { let buckets = aggregate_runtime_tracking_daily_stats( vec![ stat("daily_login", "user-1", 20_517, 1), stat("daily_login", "user-1", 20_518, 2), stat("daily_login", "user-2", 20_517, 9), stat("other", "user-1", 20_517, 9), ], "daily_login", RuntimeTrackingScopeKind::User, "user-1", AnalyticsGranularity::Day, ); assert_eq!(buckets.len(), 2); assert_eq!(buckets[0].bucket_key, "2026-03-05"); assert_eq!(buckets[0].bucket_start_date_key, 20_517); assert_eq!(buckets[0].bucket_end_date_key, 20_517); assert_eq!(buckets[0].value, 1); assert_eq!(buckets[1].bucket_key, "2026-03-06"); assert_eq!(buckets[1].value, 2); }