Added unit tests for weather module
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user