From 1e2851103fd07b820e433af16b50ada5e17db15d Mon Sep 17 00:00:00 2001 From: Mahesh Asolkar Date: Sun, 27 Jul 2025 14:54:05 -0700 Subject: [PATCH] Added unit tests for weather module --- src/bar_modules/bar_module_weather.rs | 387 ++++++++++++++++++++++++++ 1 file changed, 387 insertions(+) diff --git a/src/bar_modules/bar_module_weather.rs b/src/bar_modules/bar_module_weather.rs index fe98a36..e469a83 100644 --- a/src/bar_modules/bar_module_weather.rs +++ b/src/bar_modules/bar_module_weather.rs @@ -343,3 +343,390 @@ impl bar_modules::BarModuleDebug for UnibarModuleWeather { self.show_icons(); } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::bar_modules::BarModuleActions; // Add this line + use crate::common::AppOptions; + use crate::common::TemperatureUnits; + + /// Creates a default AppOptions instance for testing + fn create_test_options(metric: bool) -> AppOptions { + AppOptions { + interval: 1, + weather_units: if metric { TemperatureUnits::Metric } else { TemperatureUnits::Imperial }, + weather_station: "test_station".to_string(), + music_progress: false, + debug: false, + debug_modules: false, + debug_update: false, + } + } + + #[test] + /// Test get_current_temperature returns correct value for metric and imperial units + fn test_get_current_temperature_units() { + let mut metric_module = UnibarModuleWeather::new(create_test_options(true)); + let mut imperial_module = UnibarModuleWeather::new(create_test_options(false)); + + metric_module.weather_info = json!({ + "features": [{ + "properties": { + "temperature": { + "value": 25.0 + } + } + }] + }); + imperial_module.weather_info = metric_module.weather_info.clone(); + + assert_eq!(metric_module.get_current_temperature().unwrap(), 25.0); + assert!((imperial_module.get_current_temperature().unwrap() - 77.0).abs() < 0.01); + } + + #[test] + /// Test get_unicode_symbol returns default for unknown condition + fn test_get_unicode_symbol_default() { + let module = UnibarModuleWeather::new(create_test_options(true)); + assert_eq!(module.get_unicode_symbol("not_a_real_condition"), "🌤️"); + } + + #[test] + /// Test clear resets weather_info to Null + fn test_clear_resets_weather_info() { + let mut module = UnibarModuleWeather::new(create_test_options(true)); + module.weather_info = json!({"features": []}); + module.clear(); + assert_eq!(module.weather_info, json!(serde_json::Value::Null)); + } + + #[test] + /// Test get_content returns formatted temperature string + fn test_get_content_format() { + let mut module = UnibarModuleWeather::new(create_test_options(true)); + module.weather_info = json!({ + "features": [{ + "properties": { + "temperature": { + "value": 18.7 + } + } + }] + }); + let content = module.get_content(); + assert!(content.starts_with("19°C")); + } + + #[test] + /// Test get_icon returns fallback when icon is missing + fn test_get_icon_missing_icon() { + let mut module = UnibarModuleWeather::new(create_test_options(true)); + module.weather_info = json!({ + "features": [{ + "properties": { + "icon": serde_json::Value::Null + } + }] + }); + assert_eq!(module.get_icon(), "⁈"); + } + + #[test] + /// Test get_icon returns fallback when weather_info is Null + fn test_get_icon_null_weather_info() { + let module = UnibarModuleWeather::new(create_test_options(true)); + assert_eq!(module.get_icon(), "⁈"); + } + + #[test] + /// Test weather module initialization + fn test_module_initialization() { + let module = UnibarModuleWeather::new(create_test_options(true)); + assert_eq!(module.update_cnt, 0); + assert_eq!(module.weather_info, json!(serde_json::Value::Null)); + } + + #[test] + /// Test temperature unit display + fn test_temperature_unit_display() { + let metric_module = UnibarModuleWeather::new(create_test_options(true)); + let imperial_module = UnibarModuleWeather::new(create_test_options(false)); + + assert_eq!(metric_module.get_temperature_unit(), "°C"); + assert_eq!(imperial_module.get_temperature_unit(), "°F"); + } + + #[test] + /// Test weather condition to Unicode symbol mapping + fn test_unicode_symbol_mapping() { + let module = UnibarModuleWeather::new(create_test_options(true)); + + assert_eq!(module.get_unicode_symbol("snow"), "❄️"); + assert_eq!(module.get_unicode_symbol("ovc"), "☁️"); + assert_eq!(module.get_unicode_symbol("rain"), "🌧️"); + assert_eq!(module.get_unicode_symbol("unknown"), "🌤️"); + } + + #[test] + /// Test error handling for invalid temperature data + fn test_error_handling() { + let mut module = UnibarModuleWeather::new(create_test_options(true)); + + // Test null weather info + assert!(module.get_current_temperature().is_ok()); + assert_eq!(module.get_current_temperature().unwrap(), 0.0); + + // Test invalid JSON structure + module.weather_info = json!({ + "features": [{ + "properties": { + "temperature": { + "value": "invalid" + } + } + }] + }); + assert!(module.get_current_temperature().is_err()); + } + + #[test] + /// Test update counter behavior. Update only when update_cnt==0 + fn test_update_counter() { + let mut module = UnibarModuleWeather::new(create_test_options(true)); + + assert_eq!(module.update_cnt, 0); + assert_eq!(module.should_update(), UpdateResult::Update); + + module.update_cnt = UPDATE_INTERVAL - 1; + assert_eq!(module.should_update(), UpdateResult::Skip); + + module.update_cnt = UPDATE_INTERVAL; + assert_eq!(module.should_update(), UpdateResult::Skip); + } + + #[test] + /// Test JSON parsing with valid weather data + fn test_valid_weather_data_parsing() { + let mut module = UnibarModuleWeather::new(create_test_options(true)); + + // Simulate valid weather data + module.weather_info = json!({ + "features": [{ + "properties": { + "temperature": { + "value": "20.5" + } + } + }] + }); + + //println!("{:#?}", module.weather_info); + let temp = module.get_current_temperature().unwrap(); + assert_eq!(temp, 20.5); + } + + #[test] + /// Test icon extraction from weather API URL + fn test_icon_extraction() { + let mut module = UnibarModuleWeather::new(create_test_options(true)); + + // Simulate weather data with icon URL + module.weather_info = json!({ + "features": [{ + "properties": { + "icon": "https://api.weather.gov/icons/land/night/ovc?size=medium" + } + }] + }); + + assert_eq!(module.get_icon(), "☁️"); + assert_eq!(module.get_icon(), "☁"); + } + + #[test] + /// Test handling of malformed/incomplete JSON structures + fn test_missing_json_structure() { + let mut module = UnibarModuleWeather::new(create_test_options(true)); + + // Test cases with increasingly incomplete JSON structures + let test_cases = vec![ + // Missing value field + json!({ + "features": [{ + "properties": { + "temperature": {} + } + }] + }), + // Missing temperature object + json!({ + "features": [{ + "properties": {} + }] + }), + // Missing properties + json!({ + "features": [{}] + }), + // Empty features array + json!({ + "features": [] + }), + // Missing features + json!({}), + // Completely different structure + json!({ + "unexpected": "structure" + }), + // Empty object + json!({}), + // Null value + json!(null), + // Array instead of object + json!([1, 2, 3]), + // Wrong data types + json!({ + "features": [{ + "properties": { + "temperature": { + "value": true // boolean instead of number + } + } + }] + }), + ]; + + for (i, test_case) in test_cases.into_iter().enumerate() { + module.weather_info = test_case; + let result = module.get_current_temperature(); + + match result { + Ok(0.0) => { + // This is fine - we expect 0.0 for null weather_info + assert_eq!(module.weather_info, json!(null), + "Case {}: Should return 0.0 only for null weather_info", i); + }, + Err(WeatherError::ApiResponseStrErr(_)) => { + // This is also fine - we expect ApiResponseStrErr for malformed data + }, + other => { + panic!("Case {}: Unexpected result: {:?}", i, other); + } + } + } + } + + #[test] + /// Test handling of malformed temperature values + fn test_invalid_temperature_values() { + let mut module = UnibarModuleWeather::new(create_test_options(true)); + + let test_cases = vec![ + // Invalid temperature values + json!({ + "features": [{ + "properties": { + "temperature": { + "value": "not a number" + } + } + }] + }), + json!({ + "features": [{ + "properties": { + "temperature": { + "value": "" + } + } + }] + }), + json!({ + "features": [{ + "properties": { + "temperature": { + "value": null + } + } + }] + }), + json!({ + "features": [{ + "properties": { + "temperature": { + "value": {} + } + } + }] + }), + json!({ + "features": [{ + "properties": { + "temperature": { + "value": [] + } + } + }] + }) + ]; + + for (i, test_case) in test_cases.into_iter().enumerate() { + module.weather_info = test_case; + let result = module.get_current_temperature(); + + assert!(matches!(result, Err(WeatherError::ApiResponseStrErr(_))), + "Case {}: Expected ApiResponseStrErr for invalid temperature value", i); + } + } + + #[test] + /// Test that deep nested access is safe + fn test_deep_nested_access() { + let mut module = UnibarModuleWeather::new(create_test_options(true)); + + // Test accessing nested fields that don't exist + let test_cases = vec![ + // Deeper nesting than expected + json!({ + "features": [{ + "properties": { + "temperature": { + "value": { + "nested": "too deep" + } + } + } + }] + }), + // Missing intermediate fields + json!({ + "features": [{ + "properties": { + "temperature": { + "missing_value": 20.5 + } + } + }] + }), + // Wrong nesting structure + json!({ + "features": { // Object instead of array + "properties": { + "temperature": { + "value": 20.5 + } + } + } + }) + ]; + + for (i, test_case) in test_cases.into_iter().enumerate() { + module.weather_info = test_case; + let result = module.get_current_temperature(); + + assert!(matches!(result, Err(WeatherError::ApiResponseStrErr(_))), + "Case {}: Expected ApiResponseStrErr for invalid nested structure", i); + } + } +}