diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 998b7d2..a84f887 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -79,7 +79,7 @@ jobs: - name: Install linting tools run: | python -m pip install --upgrade pip - pip install flake8 black isort mypy pytest + pip install flake8 black isort mypy pytest bandit types-aiofiles - name: Run Black (code formatting check) id: black diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0630086..4343820 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: rev: 7.3.0 hooks: - id: flake8 - args: ["--max-line-length=88", "--extend-ignore=E203,W503,E501"] + args: ["--max-line-length=88", "--extend-ignore=E203,W503"] files: ^(custom_components|tests)/.*\.py$ # Type checking diff --git a/custom_components/satcom_forecast/__init__.py b/custom_components/satcom_forecast/__init__.py index 00baefb..ec3c329 100644 --- a/custom_components/satcom_forecast/__init__.py +++ b/custom_components/satcom_forecast/__init__.py @@ -27,8 +27,7 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> new = {**config_entry.data} if "polling_interval" not in new: new["polling_interval"] = 5 # Default to 5 minutes - hass.config_entries.async_update_entry(config_entry, data=new) - config_entry.version = 3 + hass.config_entries.async_update_entry(config_entry, data=new, version=3) _LOGGER.debug("Migration to version 3 successful") if config_entry.version == 3: @@ -36,8 +35,7 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> new = {**config_entry.data} if "imap_security" not in new: new["imap_security"] = "SSL" # Default to SSL for security - hass.config_entries.async_update_entry(config_entry, data=new) - config_entry.version = 4 + hass.config_entries.async_update_entry(config_entry, data=new, version=4) _LOGGER.debug("Migration to version 4 successful") return True diff --git a/tests/test_api_cache.py b/tests/test_api_cache.py index 2426769..78fa921 100644 --- a/tests/test_api_cache.py +++ b/tests/test_api_cache.py @@ -72,7 +72,8 @@ async def test_cache_eviction(cache): assert len(cache._cache) == 5 - # endpoint_1 should be gone (it was inserted second, but endpoint_0 was accessed recently) + # endpoint_1 should be gone (it was inserted second, but endpoint_0 was accessed + # recently) # Insertion order: 0, 1, 2, 3, 4. # Access: 0. # LRU order: 1, 2, 3, 4, 0. diff --git a/tests/test_api_compatibility.py b/tests/test_api_compatibility.py index 13810f1..add244b 100644 --- a/tests/test_api_compatibility.py +++ b/tests/test_api_compatibility.py @@ -192,7 +192,9 @@ async def test_weather_event_detection_compatibility(self): start_time="2024-01-01T06:00:00-05:00", end_time="2024-01-01T18:00:00-05:00", is_daytime=True, - detailed_forecast="Rain likely with a high near 70. Chance of precipitation is 80%.", + detailed_forecast=( + "Rain likely with a high near 70. Chance of precipitation is 80%." + ), ), ForecastPeriod( name="Tonight", @@ -206,7 +208,9 @@ async def test_weather_event_detection_compatibility(self): start_time="2024-01-02T06:00:00-05:00", end_time="2024-01-02T18:00:00-05:00", is_daytime=True, - detailed_forecast="Snow likely with a high near 30. Heavy snow possible.", + detailed_forecast=( + "Snow likely with a high near 30. Heavy snow possible." + ), ), ] @@ -321,7 +325,8 @@ async def test_character_count_targets(self, test_coordinates): assert len(full) >= 500, f"Full length {len(full)} below minimum 500" print( - f"✓ {lat}, {lon}: Summary={len(summary)}, Compact={len(compact)}, Full={len(full)}" + f"✓ {lat}, {lon}: Summary={len(summary)}, " + f"Compact={len(compact)}, Full={len(full)}" ) except Exception as e: @@ -365,7 +370,8 @@ async def test_days_parameter_compatibility(self): assert len(api_forecast) > 50 print( - f"Days {days}: HTML={len(html_forecast)} chars, API={len(api_forecast)} chars" + f"Days {days}: HTML={len(html_forecast)} chars, " + f"API={len(api_forecast)} chars" ) except Exception as e: diff --git a/tests/test_api_data_processor.py b/tests/test_api_data_processor.py index 9b99380..8b14a3c 100644 --- a/tests/test_api_data_processor.py +++ b/tests/test_api_data_processor.py @@ -33,7 +33,9 @@ def sample_api_response(self): "windSpeed": "10 to 15 mph", "windDirection": "NW", "shortForecast": "Sunny", - "detailedForecast": "Sunny with a high near 75. Northwest wind 10 to 15 mph.", + "detailedForecast": ( + "Sunny with a high near 75. Northwest wind 10 to 15 mph." + ), "probabilityOfPrecipitation": {"value": 0}, "relativeHumidity": {"value": 45}, "heatIndex": None, @@ -54,7 +56,9 @@ def sample_api_response(self): "windSpeed": "5 to 10 mph", "windDirection": "NW", "shortForecast": "Clear", - "detailedForecast": "Clear with a low around 55. Northwest wind 5 to 10 mph.", + "detailedForecast": ( + "Clear with a low around 55. Northwest wind 5 to 10 mph." + ), "probabilityOfPrecipitation": {"value": 0}, "relativeHumidity": {"value": 60}, "heatIndex": None, @@ -100,7 +104,9 @@ def test_extract_weather_events_rain(self, processor): start_time="2024-01-01T06:00:00-05:00", end_time="2024-01-01T18:00:00-05:00", is_daytime=True, - detailed_forecast="Rain likely with a high near 70. Chance of precipitation is 80%.", + detailed_forecast=( + "Rain likely with a high near 70. Chance of precipitation is 80%." + ), ) events = processor.extract_weather_events(period) diff --git a/tests/test_api_error_handler.py b/tests/test_api_error_handler.py index 0897bf8..a1a3ab7 100644 --- a/tests/test_api_error_handler.py +++ b/tests/test_api_error_handler.py @@ -80,9 +80,11 @@ async def test_handle_error_retry(error_handler): # Second call calls retry_func again. It returns "Success". # Wait, retry_func is called inside handle_error. - # First call to handle_error: context.retry_count=0. Increments to 1. Calls retry_func. + # First call to handle_error: context.retry_count=0. Increments to 1. + # Calls retry_func. # retry_func raises. handle_error catches and recurses. - # Second call to handle_error: context.retry_count=1. Increments to 2. Calls retry_func. + # Second call to handle_error: context.retry_count=1. Increments to 2. + # Calls retry_func. # retry_func returns "Success". # So retry_func called twice. diff --git a/tests/test_api_formatter.py b/tests/test_api_formatter.py index 504ccf0..46b1b03 100644 --- a/tests/test_api_formatter.py +++ b/tests/test_api_formatter.py @@ -31,7 +31,9 @@ def sample_periods(self): wind_speed="10 to 15 mph", wind_direction="NW", short_forecast="Sunny", - detailed_forecast="Sunny with a high near 75. Northwest wind 10 to 15 mph.", + detailed_forecast=( + "Sunny with a high near 75. Northwest wind 10 to 15 mph." + ), probability_of_precipitation=0, ), ForecastPeriod( @@ -44,7 +46,9 @@ def sample_periods(self): wind_speed="5 to 10 mph", wind_direction="NW", short_forecast="Clear", - detailed_forecast="Clear with a low around 55. Northwest wind 5 to 10 mph.", + detailed_forecast=( + "Clear with a low around 55. Northwest wind 5 to 10 mph." + ), probability_of_precipitation=0, ), ] @@ -151,7 +155,9 @@ def test_detect_period_events_rain(self, formatter, sample_events): start_time="2024-01-01T06:00:00-05:00", end_time="2024-01-01T18:00:00-05:00", is_daytime=True, - detailed_forecast="Rain likely with a high near 70. Chance of precipitation is 80%.", + detailed_forecast=( + "Rain likely with a high near 70. Chance of precipitation is 80%." + ), ) events = formatter._detect_period_events(period, sample_events) @@ -347,7 +353,10 @@ def test_clean_forecast_text(self, formatter): def test_clean_forecast_for_display(self, formatter): """Test forecast text cleaning for display.""" - text = "Sunny with a high near 75. Northwest wind 10 to 15 mph. Chance of precipitation is 0%." + text = ( + "Sunny with a high near 75. Northwest wind 10 to 15 mph. " + "Chance of precipitation is 0%." + ) cleaned = formatter._clean_forecast_for_display(text) # Should remove temperature and wind patterns @@ -401,7 +410,9 @@ def test_format_compact_forecast_with_events(self, formatter): temperature=75, wind_speed="15 mph", wind_direction="NW", - detailed_forecast="Rain likely with a high near 75. Northwest wind 15 mph.", + detailed_forecast=( + "Rain likely with a high near 75. Northwest wind 15 mph." + ), ) ] events = [ diff --git a/tests/test_api_integration.py b/tests/test_api_integration.py index fe17dcc..6592a7a 100644 --- a/tests/test_api_integration.py +++ b/tests/test_api_integration.py @@ -85,7 +85,8 @@ async def test_get_forecast_real_api(self, client): assert "isDaytime" in first_period print( - f"Retrieved forecast with {len(forecast_data['properties']['periods'])} periods" + f"Retrieved forecast with {len(forecast_data['properties']['periods'])} " + f"periods" ) @pytest.mark.asyncio @@ -245,8 +246,10 @@ async def test_caching_functionality(self, fetcher): assert forecast1 == forecast2 # Should be identical - # Only assert timing if first call took significant time (indicating network request) - # If first call was extremely fast (< 10ms), it might have been cached externally or mocked + # Only assert timing if first call took significant time (indicating network + # request) + # If first call was extremely fast (< 10ms), it might have been cached + # externally or mocked if first_call_time > 0.01: assert second_call_time < first_call_time # Should be faster diff --git a/tests/test_compact_format.py b/tests/test_compact_format.py index 0d2f55b..2c84f92 100644 --- a/tests/test_compact_format.py +++ b/tests/test_compact_format.py @@ -33,25 +33,35 @@ class TestCompactFormat: @pytest.mark.skipif(not HAS_HA, reason="Home Assistant not available") def test_compact_format_newlines(self): """Test that compact format preserves newlines between days.""" - sample_forecast = """Tonight: Rain showers likely, mainly before 7am. Low around 52. Southeast wind 5 mph. Chance of precipitation is 80%. -Showers likely, mainly before 7am. -Tuesday: Rain showers likely, mainly before 7am. High near 64. Southeast wind 5 mph. Chance of precipitation is 60%. -Showers likely, mainly before 7am -Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph. -Wednesday: Partly sunny, with a high near 63. East wind 5 mph. -Wednesday Night: A chance of rain between 10pm and 1am. Low around 52. Southeast wind 5 to 10 mph. Chance of precipitation is 30%. -A chance of rain between 10pm and 1am -Thursday: A chance of rain between 7am and 1pm. High near 62. West wind 5 mph. Chance of precipitation is 30%. -A chance of rain between 7am and 1pm -Thursday Night: Mostly cloudy, with a low around 49. -Friday: A chance of rain after 10am. High near 65. Chance of precipitation is 30%. -A chance of rain after 10am -Friday Night: A chance of rain. Low around 50. Chance of precipitation is 30%. -A chance of rain -Saturday: A chance of rain. High near 63. Chance of precipitation is 30%. -A chance of rain -Saturday Night: A chance of rain. Low around 50. Chance of precipitation is 30%. -A chance of rain""" + sample_forecast = ( + "Tonight: Rain showers likely, mainly before 7am. Low around 52. " + "Southeast wind 5 mph. Chance of precipitation is 80%.\n" + "Showers likely, mainly before 7am.\n" + "Tuesday: Rain showers likely, mainly before 7am. High near 64. " + "Southeast wind 5 mph. Chance of precipitation is 60%.\n" + "Showers likely, mainly before 7am\n" + "Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph.\n" + "Wednesday: Partly sunny, with a high near 63. East wind 5 mph.\n" + "Wednesday Night: A chance of rain between 10pm and 1am. Low around 52. " + "Southeast wind 5 to 10 mph. Chance of precipitation is 30%.\n" + "A chance of rain between 10pm and 1am\n" + "Thursday: A chance of rain between 7am and 1pm. High near 62. " + "West wind 5 mph. Chance of precipitation is 30%.\n" + "A chance of rain between 7am and 1pm\n" + "Thursday Night: Mostly cloudy, with a low around 49.\n" + "Friday: A chance of rain after 10am. High near 65. " + "Chance of precipitation is 30%.\n" + "A chance of rain after 10am\n" + "Friday Night: A chance of rain. Low around 50. " + "Chance of precipitation is 30%.\n" + "A chance of rain\n" + "Saturday: A chance of rain. High near 63. " + "Chance of precipitation is 30%.\n" + "A chance of rain\n" + "Saturday Night: A chance of rain. Low around 50. " + "Chance of precipitation is 30%.\n" + "A chance of rain" + ) # Format the compact forecast compact_result = format_compact_forecast(sample_forecast) @@ -88,9 +98,15 @@ def test_compact_format_newlines(self): @pytest.mark.skipif(not HAS_HA, reason="Home Assistant not available") def test_compact_format_no_pipe_separators(self): - """Test that compact format doesn't use pipe separators that would trigger summary format detection.""" - sample_forecast = """Tonight: Rain showers likely, mainly before 7am. Low around 52. Southeast wind 5 mph. Chance of precipitation is 80%. -Tuesday: Rain showers likely, mainly before 7am. High near 64. Southeast wind 5 mph. Chance of precipitation is 60%.""" + """Test that compact format doesn't use pipe separators that would + trigger summary format detection. + """ + sample_forecast = ( + "Tonight: Rain showers likely, mainly before 7am. Low around 52. " + "Southeast wind 5 mph. Chance of precipitation is 80%.\n" + "Tuesday: Rain showers likely, mainly before 7am. High near 64. " + "Southeast wind 5 mph. Chance of precipitation is 60%." + ) # Format the compact forecast compact_result = format_compact_forecast(sample_forecast) @@ -107,11 +123,17 @@ def test_compact_format_no_pipe_separators(self): @pytest.mark.skipif(not HAS_HA, reason="Home Assistant not available") def test_compact_format_splitting(self): - """Test that compact format is correctly detected and split by the split_message function.""" - sample_forecast = """Tonight: Rain showers likely, mainly before 7am. Low around 52. Southeast wind 5 mph. Chance of precipitation is 80%. -Tuesday: Rain showers likely, mainly before 7am. High near 64. Southeast wind 5 mph. Chance of precipitation is 60%. -Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph. -Wednesday: Partly sunny, with a high near 63. East wind 5 mph.""" + """Test that compact format is correctly detected and split by the + split_message function. + """ + sample_forecast = ( + "Tonight: Rain showers likely, mainly before 7am. Low around 52. " + "Southeast wind 5 mph. Chance of precipitation is 80%.\n" + "Tuesday: Rain showers likely, mainly before 7am. High near 64. " + "Southeast wind 5 mph. Chance of precipitation is 60%.\n" + "Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph.\n" + "Wednesday: Partly sunny, with a high near 63. East wind 5 mph." + ) # Format the compact forecast compact_result = format_compact_forecast(sample_forecast) @@ -159,9 +181,13 @@ def test_compact_format_splitting(self): @pytest.mark.skipif(not HAS_HA, reason="Home Assistant not available") def test_compact_format_day_separation(self): """Test that days are properly separated and don't run together.""" - sample_forecast = """Tonight: Rain showers likely, mainly before 7am. Low around 52. Southeast wind 5 mph. Chance of precipitation is 80%. -Tuesday: Rain showers likely, mainly before 7am. High near 64. Southeast wind 5 mph. Chance of precipitation is 60%. -Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph.""" + sample_forecast = ( + "Tonight: Rain showers likely, mainly before 7am. Low around 52. " + "Southeast wind 5 mph. Chance of precipitation is 80%.\n" + "Tuesday: Rain showers likely, mainly before 7am. High near 64. " + "Southeast wind 5 mph. Chance of precipitation is 60%.\n" + "Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph." + ) # Format the compact forecast compact_result = format_compact_forecast(sample_forecast) diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index 60fd495..76495dc 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -302,13 +302,15 @@ async def test_options_flow_update(mock_hass, mock_config_entry): "imap_folder": "INBOX", } - # Mock validate_imap_folder (since folder didn't change, it shouldn't be called, but just in case) + # Mock validate_imap_folder (since folder didn't change, it shouldn't be called, + # but just in case) mock_hass.async_add_executor_job.return_value = (True, None) # Since we are using MockConfigFlow, async_create_entry is already defined there # But we need to mock async_reload_entry import inside the method # The method does: from . import async_reload_entry - # We can mock sys.modules['custom_components.satcom_forecast'] to have async_reload_entry + # We can mock sys.modules['custom_components.satcom_forecast'] to have + # async_reload_entry async def mock_reload(*args, **kwargs): pass diff --git a/tests/test_forecast_parser.py b/tests/test_forecast_parser.py index cd0fe2e..595f45b 100644 --- a/tests/test_forecast_parser.py +++ b/tests/test_forecast_parser.py @@ -11,19 +11,30 @@ ) from forecast_parser import format_forecast, summarize_forecast # noqa: E402 -TEST_FORECAST = """This Afternoon: A chance of showers. Mostly cloudy, with a high near 50. Southeast wind around 10 mph. Chance of precipitation is 40%. -Tonight: Showers likely, mainly between 7pm and 10pm, then rain after 10pm. Low around 41. Southeast wind around 15 mph. Chance of precipitation is 80%. -Thursday: Rain likely. Cloudy, with a high near 49. Southeast wind 10 to 15 mph. Chance of precipitation is 70%. -Thursday Night: Rain likely, mainly before 10pm. Cloudy, with a low around 41. Southeast wind 5 to 15 mph. Chance of precipitation is 60%. -Friday: Rain likely, mainly between 10am and 1pm. Cloudy, with a high near 51. Southeast wind around 10 mph. Chance of precipitation is 60%. -Friday Night: A chance of rain before 7pm, then scattered showers after 7pm. Cloudy, with a low around 43. Southeast wind around 10 mph. Chance of precipitation is 50%. -Saturday: Scattered showers before 10am, then a chance of rain after 10am. Cloudy, with a high near 52. Chance of precipitation is 30%. -Saturday Night: A chance of rain. Cloudy, with a low around 43. -Sunday: A chance of rain. Cloudy, with a high near 52. -Sunday Night: A chance of rain. Cloudy, with a low around 43. -Monday: A chance of rain. Mostly cloudy, with a high near 54. -Monday Night: A chance of rain. Mostly cloudy, with a low around 44. -Tuesday: A chance of rain. Mostly cloudy, with a high near 56.""" +TEST_FORECAST = ( + "This Afternoon: A chance of showers. Mostly cloudy, with a high near 50. " + "Southeast wind around 10 mph. Chance of precipitation is 40%.\n" + "Tonight: Showers likely, mainly between 7pm and 10pm, then rain after 10pm. " + "Low around 41. Southeast wind around 15 mph. " + "Chance of precipitation is 80%.\n" + "Thursday: Rain likely. Cloudy, with a high near 49. " + "Southeast wind 10 to 15 mph. Chance of precipitation is 70%.\n" + "Thursday Night: Rain likely, mainly before 10pm. Cloudy, with a low " + "around 41. Southeast wind 5 to 15 mph. Chance of precipitation is 60%.\n" + "Friday: Rain likely, mainly between 10am and 1pm. Cloudy, with a high " + "near 51. Southeast wind around 10 mph. Chance of precipitation is 60%.\n" + "Friday Night: A chance of rain before 7pm, then scattered showers after 7pm. " + "Cloudy, with a low around 43. Southeast wind around 10 mph. " + "Chance of precipitation is 50%.\n" + "Saturday: Scattered showers before 10am, then a chance of rain after 10am. " + "Cloudy, with a high near 52. Chance of precipitation is 30%.\n" + "Saturday Night: A chance of rain. Cloudy, with a low around 43.\n" + "Sunday: A chance of rain. Cloudy, with a high near 52.\n" + "Sunday Night: A chance of rain. Cloudy, with a low around 43.\n" + "Monday: A chance of rain. Mostly cloudy, with a high near 54.\n" + "Monday Night: A chance of rain. Mostly cloudy, with a low around 44.\n" + "Tuesday: A chance of rain. Mostly cloudy, with a high near 56." +) class TestForecastParser: @@ -40,8 +51,11 @@ def test_basic_formats(self) -> None: def test_smoke_conditions(self) -> None: """Test smoke detection and labeling.""" - smoke_forecast = """This Afternoon: Widespread haze. Mostly cloudy, with a high near 50. Southeast wind around 10 mph. -Tonight: Widespread haze. Low around 41. Southeast wind around 15 mph.""" + smoke_forecast = ( + "This Afternoon: Widespread haze. Mostly cloudy, with a high near 50. " + "Southeast wind around 10 mph.\n" + "Tonight: Widespread haze. Low around 41. Southeast wind around 15 mph." + ) areas_smoke_forecast = ( "This Afternoon: Areas of smoke. Mostly sunny, with a high near 70." ) @@ -56,8 +70,13 @@ def test_smoke_conditions(self) -> None: def test_explicit_percentages(self) -> None: """Test explicit percentage handling.""" - explicit_forecast = """This Afternoon: A chance of showers. Mostly cloudy, with a high near 50. Southeast wind around 10 mph. Chance of precipitation is 40%. -Tonight: Showers likely, mainly between 7pm and 10pm, then rain after 10pm. Low around 41. Southeast wind around 15 mph. Chance of precipitation is 80%.""" + explicit_forecast = ( + "This Afternoon: A chance of showers. Mostly cloudy, with a high near 50. " + "Southeast wind around 10 mph. Chance of precipitation is 40%.\n" + "Tonight: Showers likely, mainly between 7pm and 10pm, then rain after " + "10pm. Low around 41. Southeast wind around 15 mph. " + "Chance of precipitation is 80%." + ) compact_result = format_forecast(explicit_forecast, mode="compact") summary_result = format_forecast(explicit_forecast, mode="summary") @@ -68,8 +87,14 @@ def test_explicit_percentages(self) -> None: def test_smoke_with_precipitation(self) -> None: """Test that smoke and precipitation don't share percentages.""" - test_text = """This Afternoon: Scattered showers. Areas of smoke. Mostly cloudy, with a high near 67. South wind around 5 mph. Chance of precipitation is 40%. -Tonight: Showers, mainly before 1am, then rain after 1am. Areas of smoke. Mostly cloudy, with a low around 57. Southeast wind around 5 mph. Chance of precipitation is 80%.""" + test_text = ( + "This Afternoon: Scattered showers. Areas of smoke. Mostly cloudy, " + "with a high near 67. South wind around 5 mph. " + "Chance of precipitation is 40%.\n" + "Tonight: Showers, mainly before 1am, then rain after 1am. " + "Areas of smoke. Mostly cloudy, with a low around 57. " + "Southeast wind around 5 mph. Chance of precipitation is 80%." + ) compact_result = format_forecast(test_text, mode="compact") summary_result = format_forecast(test_text, mode="summary") @@ -118,10 +143,16 @@ def test_weather_event_detection( def test_temperature_wind_extraction(self) -> None: """Test temperature and wind information extraction.""" - temp_wind_forecast = """Tonight: A chance of rain. Mostly cloudy, with a low around 46. Southeast wind 5 to 10 mph. Chance of precipitation is 30%. -Wednesday: Rain likely. Cloudy, with a high near 61. Southeast wind 5 to 10 mph. Chance of precipitation is 70%. -Wednesday Night: Rain. Cloudy, with a low around 45. East wind around 15 mph. Chance of precipitation is 80%. -Thursday: Rain. Cloudy, with a high near 61. East wind 5 to 10 mph. Chance of precipitation is 90%.""" + temp_wind_forecast = ( + "Tonight: A chance of rain. Mostly cloudy, with a low around 46. " + "Southeast wind 5 to 10 mph. Chance of precipitation is 30%.\n" + "Wednesday: Rain likely. Cloudy, with a high near 61. " + "Southeast wind 5 to 10 mph. Chance of precipitation is 70%.\n" + "Wednesday Night: Rain. Cloudy, with a low around 45. " + "East wind around 15 mph. Chance of precipitation is 80%.\n" + "Thursday: Rain. Cloudy, with a high near 61. East wind 5 to 10 mph. " + "Chance of precipitation is 90%." + ) summary = summarize_forecast(temp_wind_forecast) @@ -163,34 +194,61 @@ def test_smoke_probability_levels(self) -> None: def test_summary_with_temp_wind(self) -> None: """Test summary format with temperature and wind information.""" - sample_text = """ -Tonight: A chance of rain. Mostly cloudy, with a low around 46. Southeast wind 5 to 10 mph. Chance of precipitation is 30%. -Wednesday: Rain likely. Cloudy, with a high near 61. Southeast wind 5 to 10 mph. Chance of precipitation is 70%. -Wednesday Night: Rain. Cloudy, with a low around 45. East wind around 15 mph. Chance of precipitation is 80%. -Thursday: Rain. Cloudy, with a high near 61. East wind 5 to 10 mph. Chance of precipitation is 90%. -""" + sample_text = ( + "\n" + "Tonight: A chance of rain. Mostly cloudy, with a low around 46. " + "Southeast wind 5 to 10 mph. Chance of precipitation is 30%.\n" + "Wednesday: Rain likely. Cloudy, with a high near 61. " + "Southeast wind 5 to 10 mph. Chance of precipitation is 70%.\n" + "Wednesday Night: Rain. Cloudy, with a low around 45. " + "East wind around 15 mph. Chance of precipitation is 80%.\n" + "Thursday: Rain. Cloudy, with a high near 61. East wind 5 to 10 mph. " + "Chance of precipitation is 90%.\n" + ) summary = summarize_forecast(sample_text) - expected_summary = "Tngt: Rn(30%),L:46°,SE5-10mph\nWed: Rn(80%),H:61°,SE5-10mph,L:45°,E15mph\nThu: Rn(90%),H:61°,E5-10mph" # Updated to include spaces after colons + expected_summary = ( + "Tngt: Rn(30%),L:46°,SE5-10mph\n" + "Wed: Rn(80%),H:61°,SE5-10mph,L:45°,E15mph\n" + "Thu: Rn(90%),H:61°,E5-10mph" + ) assert summary == expected_summary def test_real_world_summary(self) -> None: """Test a real-world forecast scenario.""" - real_forecast_text = """ -Tonight: Mostly cloudy, with a low around 46. Light and variable wind becoming southeast 5 to 10 mph in the evening. -Wednesday: A slight chance of showers after 4pm. Mostly cloudy, with a high near 61. Northwest wind around 5 mph. -Wednesday Night: A 20 percent chance of showers. Mostly cloudy, with a low around 45. Southeast wind around 15 mph. -Thursday: A 30 percent chance of rain. Mostly cloudy, with a high near 61. Southeast wind 5 to 10 mph. -Thursday Night: A 20 percent chance of rain. Mostly cloudy, with a low around 47. East wind around 10 mph. Breezy. -Friday: A 20 percent chance of rain. Mostly cloudy, with a high near 62. Northeast wind around 5 mph. -Friday Night: Mostly cloudy, with a low around 48. -Saturday: Partly sunny, with a high near 64. -Saturday Night: Mostly cloudy, with a low around 48. -Sunday: A 30 percent chance of rain. Mostly cloudy, with a high near 61. -Sunday Night: A 20 percent chance of rain. Mostly cloudy, with a low around 47. -Monday: A 30 percent chance of rain. Mostly cloudy, with a high near 61. -""" + real_forecast_text = ( + "\n" + "Tonight: Mostly cloudy, with a low around 46. Light and variable wind " + "becoming southeast 5 to 10 mph in the evening.\n" + "Wednesday: A slight chance of showers after 4pm. Mostly cloudy, " + "with a high near 61. Northwest wind around 5 mph.\n" + "Wednesday Night: A 20 percent chance of showers. Mostly cloudy, " + "with a low around 45. Southeast wind around 15 mph.\n" + "Thursday: A 30 percent chance of rain. Mostly cloudy, " + "with a high near 61. Southeast wind 5 to 10 mph.\n" + "Thursday Night: A 20 percent chance of rain. Mostly cloudy, " + "with a low around 47. East wind around 10 mph. Breezy.\n" + "Friday: A 20 percent chance of rain. Mostly cloudy, " + "with a high near 62. Northeast wind around 5 mph.\n" + "Friday Night: Mostly cloudy, with a low around 48.\n" + "Saturday: Partly sunny, with a high near 64.\n" + "Saturday Night: Mostly cloudy, with a low around 48.\n" + "Sunday: A 30 percent chance of rain. Mostly cloudy, " + "with a high near 61.\n" + "Sunday Night: A 20 percent chance of rain. Mostly cloudy, " + "with a low around 47.\n" + "Monday: A 30 percent chance of rain. Mostly cloudy, " + "with a high near 61.\n" + ) summary = summarize_forecast(real_forecast_text) - expected_summary = "Tngt: L:46°,SE5-10mph\nWed: Rn(30%),H:61°,NW5mph,L:45°,SE15mph\nThu: Rn(30%),H:61°,SE5-10mph,L:47°,E10mph\nFri: Rn(20%),H:62°,NE5mph,L:48°\nSat: H:64°,L:48°\nSun: Rn(30%),H:61°,L:47°\nMon: Rn(30%),H:61°" + expected_summary = ( + "Tngt: L:46°,SE5-10mph\n" + "Wed: Rn(30%),H:61°,NW5mph,L:45°,SE15mph\n" + "Thu: Rn(30%),H:61°,SE5-10mph,L:47°,E10mph\n" + "Fri: Rn(20%),H:62°,NE5mph,L:48°\n" + "Sat: H:64°,L:48°\n" + "Sun: Rn(30%),H:61°,L:47°\n" + "Mon: Rn(30%),H:61°" + ) # Since Tngt is the first period and it's night, it stays as Tngt. # Wed + Wed Night -> Wed # Thu + Thu Night -> Thu @@ -203,10 +261,14 @@ def test_real_world_summary(self) -> None: # "Wed: Rn(30%),H:61°,NW5mph,L:45°,SE15mph" -> This has H and L. # "Thu: Rn(30%),H:61°,SE5-10mph,L:47°,E10mph" -> This has H and L. - # The failure was: assert 'Tngt: L:46°,...Rn(30%),H:61°' == 'Tngt: L:46°,...Rn(30%),H:61°' + # The failure was: assert 'Tngt: L:46°,...Rn(30%),H:61°' == 'Tngt: L:46°,' + # '...Rn(30%),H:61°' # Wait, the failure message showed identical strings? - # FAILED tests/test_forecast_parser.py::TestForecastParser::test_real_world_summary - AssertionError: assert 'Tngt: L:46°,...Rn(30%),H:61°' == 'Tngt: L:46°,...Rn(30%),H:61°' - # This usually means there's a subtle difference (whitespace, invisible char, or order). + # FAILED tests/test_forecast_parser.py::TestForecastParser:: + # test_real_world_summary - AssertionError: assert 'Tngt: L:46°,... + # Rn(30%),H:61°' == 'Tngt: L:46°,...Rn(30%),H:61°' + # This usually means there's a subtle difference (whitespace, invisible char, + # or order). # Let's re-verify the expected string carefully. # In the actual output (from reproduction script): @@ -222,21 +284,25 @@ def test_real_world_summary(self) -> None: # Tngt: L:46°,SE5-10mph # Wed: Rn(30%),H:61°,NW5mph,L:45°,SE15mph - # I will update the expectation to be exactly what I think it should be based on the logic. + # I will update the expectation to be exactly what I think it should be + # based on the logic. assert summary == expected_summary def test_summarize_forecast_with_holidays(self) -> None: """Test that summarize_forecast correctly groups holidays and their nights.""" - forecast_text = """ -Tonight: Rain likely. Low around 12. Northeast wind 5 mph. -Wednesday: Rain likely. High near 27. Northeast wind 5 mph. -Wednesday Night: Rain likely. Low around 26. Northeast wind 5 to 10 mph. -Thanksgiving Day: Rain likely. High near 32. Northeast wind 10 mph. -Thursday Night: Rain likely. Low around 29. Northeast wind 5 to 10 mph. -Friday: Rain likely. High near 34. Northeast wind 5 to 10 mph. -Friday Night: Rain likely. Low around 23. -""" + forecast_text = ( + "\n" + "Tonight: Rain likely. Low around 12. Northeast wind 5 mph.\n" + "Wednesday: Rain likely. High near 27. Northeast wind 5 mph.\n" + "Wednesday Night: Rain likely. Low around 26. " + "Northeast wind 5 to 10 mph.\n" + "Thanksgiving Day: Rain likely. High near 32. Northeast wind 10 mph.\n" + "Thursday Night: Rain likely. Low around 29. " + "Northeast wind 5 to 10 mph.\n" + "Friday: Rain likely. High near 34. Northeast wind 5 to 10 mph.\n" + "Friday Night: Rain likely. Low around 23.\n" + ) summary = summarize_forecast(forecast_text) # Check that "Thanksgiving Day" (Tha) and "Thursday Night" are grouped diff --git a/tests/test_full_format.py b/tests/test_full_format.py index 943fee1..0f06d9e 100644 --- a/tests/test_full_format.py +++ b/tests/test_full_format.py @@ -33,12 +33,16 @@ class TestFullFormat: @pytest.mark.skipif(not HAS_HA, reason="Home Assistant not available") def test_full_format_complete_forecast(self): """Test that full format preserves complete forecast information.""" - sample_forecast = """Tonight: Rain showers likely, mainly before 7am. Low around 52. Southeast wind 5 mph. Chance of precipitation is 80%. -Showers likely, mainly before 7am. -Tuesday: Rain showers likely, mainly before 7am. High near 64. Southeast wind 5 mph. Chance of precipitation is 60%. -Showers likely, mainly before 7am -Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph. -Wednesday: Partly sunny, with a high near 63. East wind 5 mph.""" + sample_forecast = ( + "Tonight: Rain showers likely, mainly before 7am. Low around 52. " + "Southeast wind 5 mph. Chance of precipitation is 80%.\n" + "Showers likely, mainly before 7am.\n" + "Tuesday: Rain showers likely, mainly before 7am. High near 64. " + "Southeast wind 5 mph. Chance of precipitation is 60%.\n" + "Showers likely, mainly before 7am\n" + "Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph.\n" + "Wednesday: Partly sunny, with a high near 63. East wind 5 mph." + ) # Format the full forecast full_result = format_full_forecast(sample_forecast) @@ -62,9 +66,13 @@ def test_full_format_complete_forecast(self): @pytest.mark.skipif(not HAS_HA, reason="Home Assistant not available") def test_full_format_preserves_structure(self): """Test that full format preserves the original forecast structure.""" - sample_forecast = """Tonight: Rain showers likely, mainly before 7am. Low around 52. Southeast wind 5 mph. Chance of precipitation is 80%. -Showers likely, mainly before 7am. -Tuesday: Rain showers likely, mainly before 7am. High near 64. Southeast wind 5 mph. Chance of precipitation is 60%.""" + sample_forecast = ( + "Tonight: Rain showers likely, mainly before 7am. Low around 52. " + "Southeast wind 5 mph. Chance of precipitation is 80%.\n" + "Showers likely, mainly before 7am.\n" + "Tuesday: Rain showers likely, mainly before 7am. High near 64. " + "Southeast wind 5 mph. Chance of precipitation is 60%." + ) # Format the full forecast full_result = format_full_forecast(sample_forecast) @@ -101,9 +109,15 @@ def test_full_format_preserves_structure(self): @pytest.mark.skipif(not HAS_HA, reason="Home Assistant not available") def test_full_format_no_abbreviations(self): - """Test that full format doesn't use abbreviations like compact or summary formats.""" - sample_forecast = """Tonight: Rain showers likely, mainly before 7am. Low around 52. Southeast wind 5 mph. Chance of precipitation is 80%. -Tuesday: Rain showers likely, mainly before 7am. High near 64. Southeast wind 5 mph. Chance of precipitation is 60%.""" + """Test that full format doesn't use abbreviations like compact or + summary formats. + """ + sample_forecast = ( + "Tonight: Rain showers likely, mainly before 7am. Low around 52. " + "Southeast wind 5 mph. Chance of precipitation is 80%.\n" + "Tuesday: Rain showers likely, mainly before 7am. High near 64. " + "Southeast wind 5 mph. Chance of precipitation is 60%." + ) # Format the full forecast full_result = format_full_forecast(sample_forecast) @@ -131,11 +145,17 @@ def test_full_format_no_abbreviations(self): @pytest.mark.skipif(not HAS_HA, reason="Home Assistant not available") def test_full_format_splitting(self): - """Test that full format is correctly detected and split by the split_message function.""" - sample_forecast = """Tonight: Rain showers likely, mainly before 7am. Low around 52. Southeast wind 5 mph. Chance of precipitation is 80%. -Tuesday: Rain showers likely, mainly before 7am. High near 64. Southeast wind 5 mph. Chance of precipitation is 60%. -Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph. -Wednesday: Partly sunny, with a high near 63. East wind 5 mph.""" + """Test that full format is correctly detected and split by the + split_message function. + """ + sample_forecast = ( + "Tonight: Rain showers likely, mainly before 7am. Low around 52. " + "Southeast wind 5 mph. Chance of precipitation is 80%.\n" + "Tuesday: Rain showers likely, mainly before 7am. High near 64. " + "Southeast wind 5 mph. Chance of precipitation is 60%.\n" + "Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph.\n" + "Wednesday: Partly sunny, with a high near 63. East wind 5 mph." + ) # Format the full forecast full_result = format_full_forecast(sample_forecast) @@ -182,10 +202,14 @@ def test_full_format_splitting(self): @pytest.mark.skipif(not HAS_HA, reason="Home Assistant not available") def test_full_format_weather_events(self): - """Test that full format properly includes detailed weather event information.""" - sample_forecast = """Tonight: Rain showers likely, mainly before 7am. Low around 52. Southeast wind 5 mph. Chance of precipitation is 80%. -Showers likely, mainly before 7am. -Tuesday: Rain showers likely, mainly before 7am. High near 64. Southeast wind 5 mph. Chance of precipitation is 60%.""" + """Test that full format properly includes detailed weather event info.""" + sample_forecast = ( + "Tonight: Rain showers likely, mainly before 7am. Low around 52. " + "Southeast wind 5 mph. Chance of precipitation is 80%.\n" + "Showers likely, mainly before 7am.\n" + "Tuesday: Rain showers likely, mainly before 7am. High near 64. " + "Southeast wind 5 mph. Chance of precipitation is 60%." + ) # Format the full forecast full_result = format_full_forecast(sample_forecast) @@ -194,8 +218,11 @@ def test_full_format_weather_events(self): assert ( "Rain showers likely" in full_result ), "Should contain detailed precipitation information" - # Note: The detailed timing information might not be preserved in the current implementation - # assert "Showers likely, mainly before 7am" in full_result, "Should contain detailed timing information" + # Note: The detailed timing information might not be preserved in the current + # implementation + # assert "Showers likely, mainly before 7am" in full_result, ( + # "Should contain detailed timing information" + # ) assert ( "Chance of precipitation is 80%" in full_result ), "Should contain detailed probability information" @@ -206,9 +233,13 @@ def test_full_format_weather_events(self): @pytest.mark.skipif(not HAS_HA, reason="Home Assistant not available") def test_full_format_period_separation(self): """Test that periods are properly separated and don't run together.""" - sample_forecast = """Tonight: Rain showers likely, mainly before 7am. Low around 52. Southeast wind 5 mph. Chance of precipitation is 80%. -Tuesday: Rain showers likely, mainly before 7am. High near 64. Southeast wind 5 mph. Chance of precipitation is 60%. -Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph.""" + sample_forecast = ( + "Tonight: Rain showers likely, mainly before 7am. Low around 52. " + "Southeast wind 5 mph. Chance of precipitation is 80%.\n" + "Tuesday: Rain showers likely, mainly before 7am. High near 64. " + "Southeast wind 5 mph. Chance of precipitation is 60%.\n" + "Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph." + ) # Format the full forecast full_result = format_full_forecast(sample_forecast) @@ -266,9 +297,13 @@ def test_full_format_period_separation(self): @pytest.mark.skipif(not HAS_HA, reason="Home Assistant not available") def test_full_format_vs_compact_format(self): """Test that full format provides more detail than compact format.""" - sample_forecast = """Tonight: Rain showers likely, mainly before 7am. Low around 52. Southeast wind 5 mph. Chance of precipitation is 80%. -Showers likely, mainly before 7am. -Tuesday: Rain showers likely, mainly before 7am. High near 64. Southeast wind 5 mph. Chance of precipitation is 60%.""" + sample_forecast = ( + "Tonight: Rain showers likely, mainly before 7am. Low around 52. " + "Southeast wind 5 mph. Chance of precipitation is 80%.\n" + "Showers likely, mainly before 7am.\n" + "Tuesday: Rain showers likely, mainly before 7am. High near 64. " + "Southeast wind 5 mph. Chance of precipitation is 60%." + ) # Format both formats from forecast_parser import format_compact_forecast @@ -285,8 +320,11 @@ def test_full_format_vs_compact_format(self): assert ( "Chance of precipitation is 80%" in full_result ), "Full format should contain detailed probability" - # Note: The detailed timing information might not be preserved in the current implementation - # assert "Showers likely, mainly before 7am" in full_result, "Full format should contain detailed timing" + # Note: The detailed timing information might not be preserved in the current + # implementation + # assert "Showers likely, mainly before 7am" in full_result, ( + # "Full format should contain detailed timing" + # ) # Compact format should be more concise assert ( diff --git a/tests/test_imap_handler_extended.py b/tests/test_imap_handler_extended.py index 0db9453..56964ac 100644 --- a/tests/test_imap_handler_extended.py +++ b/tests/test_imap_handler_extended.py @@ -127,8 +127,8 @@ def test_check_imap_sync_multipart(self, mock_imap_cls): msg.set_content("This is text part. 34.5, -118.2") msg.add_alternative("HTML part", subtype="html") - # Convert to bytes (need to use policy or generator for older python versions if needed, - # but EmailMessage.as_bytes() should work) + # Convert to bytes (need to use policy or generator for older python versions + # if needed, but EmailMessage.as_bytes() should work) msg_bytes = msg.as_bytes() mock_imap.fetch.return_value = ("OK", [(b"1 (RFC822 {100}", msg_bytes)]) diff --git a/tests/test_init.py b/tests/test_init.py index 41bdb64..858b139 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -75,7 +75,15 @@ def return_future(*args, **kwargs): side_effect=return_future ) - hass.config_entries.async_update_entry = MagicMock() + def update_entry_side_effect(entry, data=None, version=None, **kwargs): + if data: + entry.data.update(data) + if version is not None: + entry.version = version + + hass.config_entries.async_update_entry = MagicMock( + side_effect=update_entry_side_effect + ) hass.services = MagicMock() hass.services.async_register = MagicMock() hass.services.async_remove = MagicMock() diff --git a/tests/test_naming_logic.py b/tests/test_naming_logic.py index fecfccf..c247163 100644 --- a/tests/test_naming_logic.py +++ b/tests/test_naming_logic.py @@ -65,8 +65,11 @@ def test_parser_summary_today(): def test_parser_summary_overnight(): - """Test that 'Overnight' is summarized as 'Tdy' (or kept as ON if preferred, but let's check consistency).""" - # If forecast starts with Overnight, it's technically the start of the "day" from the user's perspective if they check late? - # Or maybe it should be "ON". The user request specifically mentioned "This Afternoon" -> "Tdy". + """Test that 'Overnight' is summarized as 'Tdy' (or kept as ON if preferred, + but let's check consistency).""" + # If forecast starts with Overnight, it's technically the start of the "day" + # from the user's perspective if they check late? + # Or maybe it should be "ON". The user request specifically mentioned + # "This Afternoon" -> "Tdy". # Let's stick to "This Afternoon" for now. pass diff --git a/tests/test_percentage_context_bug.py b/tests/test_percentage_context_bug.py index 7f68b3b..d6f465d 100644 --- a/tests/test_percentage_context_bug.py +++ b/tests/test_percentage_context_bug.py @@ -70,7 +70,9 @@ def test_percentage_with_percent_word(): def test_no_percentage_near_event(): - """Test that when no percentage is near the event keyword, it falls back to keyword-based inference.""" + """Test that when no percentage is near the event keyword, it falls back to + keyword-based inference. + """ period = ForecastPeriod( name="Today", start_time="2024-01-01T06:00:00-05:00", diff --git a/tests/test_split_util_extended.py b/tests/test_split_util_extended.py index e559654..259a445 100644 --- a/tests/test_split_util_extended.py +++ b/tests/test_split_util_extended.py @@ -88,8 +88,8 @@ def test_split_line_to_fill_space_not_beneficial(self): # Wait, it tries to fill as much as possible. # If we give it space for just "A", it's 1 char. # If available space is 10. "A very" is 6. 6/10 = 0.6. - # Let's try a case where the first word is very short compared to available space, - # but the next word makes it overflow. + # Let's try a case where the first word is very short compared to available + # space, but the next word makes it overflow. # "I am" -> "I" (1). "am" (2). # If available space is 10. "I am" fits. # We need a case where current_length < available_space * 0.4 @@ -147,7 +147,10 @@ def test_find_best_break_point_priorities(self): def test_split_summary_format_aggressive(self): """Test split_summary_format splitting long segments.""" # A segment longer than limit - text = "Short | This is a very long segment that needs to be split aggressively because it exceeds the limit | End" + text = ( + "Short | This is a very long segment that needs to be split aggressively " + "because it exceeds the limit | End" + ) parts = split_summary_format(text, effective_limit=20) # Should split the middle part assert len(parts) > 3 diff --git a/tests/test_split_utility.py b/tests/test_split_utility.py index a268343..1d8f315 100644 --- a/tests/test_split_utility.py +++ b/tests/test_split_utility.py @@ -31,7 +31,9 @@ class TestSplitUtility: @pytest.mark.skipif(not HAS_HA, reason="Home Assistant not available") def test_split_multiline_text_day_separation(self): - """Test that split_multiline_text properly separates days while combining efficiently.""" + """Test that split_multiline_text properly separates days while combining + efficiently. + """ # Create a compact format text with multiple days compact_text = """Tonight: Rain showers likely, mainly before 7am (L:52, SE5mph) Tuesday: Rain showers likely, mainly before 7am (H:64, SE5mph) @@ -74,7 +76,9 @@ def test_split_multiline_text_day_separation(self): @pytest.mark.skipif(not HAS_HA, reason="Home Assistant not available") def test_split_multiline_text_efficient_combination(self): - """Test that split_multiline_text efficiently combines multiple days into single parts.""" + """Test that split_multiline_text efficiently combines multiple days into + single parts. + """ # Create a compact format text with short day entries compact_text = """Tonight: Rain (L:52) Tuesday: Rain (H:64) @@ -110,7 +114,9 @@ def test_split_multiline_text_efficient_combination(self): @pytest.mark.skipif(not HAS_HA, reason="Home Assistant not available") def test_split_message_format_detection(self): - """Test that split_message correctly detects format type and uses appropriate splitting.""" + """Test that split_message correctly detects format type and uses appropriate + splitting. + """ # Test compact format (newlines) compact_text = """Tonight: Rain showers likely, mainly before 7am (L:52, SE5mph) Tuesday: Rain showers likely, mainly before 7am (H:64, SE5mph)""" @@ -131,12 +137,20 @@ def test_split_message_format_detection(self): def test_split_message_character_limits(self): """Test that split_message respects character limits.""" # Create a long text that should be split - long_text = """Tonight: Rain showers likely, mainly before 7am. Low around 52. Southeast wind 5 mph. Chance of precipitation is 80%. Showers likely, mainly before 7am. -Tuesday: Rain showers likely, mainly before 7am. High near 64. Southeast wind 5 mph. Chance of precipitation is 60%. Showers likely, mainly before 7am. -Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph. -Wednesday: Partly sunny, with a high near 63. East wind 5 mph. -Wednesday Night: A chance of rain between 10pm and 1am. Low around 52. Southeast wind 5 to 10 mph. Chance of precipitation is 30%. -Thursday: A chance of rain between 7am and 1pm. High near 62. West wind 5 mph. Chance of precipitation is 30%.""" + long_text = ( + "Tonight: Rain showers likely, mainly before 7am. Low around 52. " + "Southeast wind 5 mph. Chance of precipitation is 80%. Showers likely, " + "mainly before 7am.\n" + "Tuesday: Rain showers likely, mainly before 7am. High near 64. " + "Southeast wind 5 mph. Chance of precipitation is 60%. Showers likely, " + "mainly before 7am.\n" + "Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph.\n" + "Wednesday: Partly sunny, with a high near 63. East wind 5 mph.\n" + "Wednesday Night: A chance of rain between 10pm and 1am. Low around 52. " + "Southeast wind 5 to 10 mph. Chance of precipitation is 30%.\n" + "Thursday: A chance of rain between 7am and 1pm. High near 62. " + "West wind 5 mph. Chance of precipitation is 30%." + ) # Split with a small limit parts = split_message(long_text, device_type="zoleo") @@ -154,12 +168,18 @@ def test_split_message_character_limits(self): def test_split_message_part_numbering(self): """Test that split_message adds part numbering when needed.""" # Create a text that will be split into multiple parts - long_text = """Tonight: Rain showers likely, mainly before 7am. Low around 52. Southeast wind 5 mph. Chance of precipitation is 80%. -Tuesday: Rain showers likely, mainly before 7am. High near 64. Southeast wind 5 mph. Chance of precipitation is 60%. -Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph. -Wednesday: Partly sunny, with a high near 63. East wind 5 mph. -Wednesday Night: A chance of rain between 10pm and 1am. Low around 52. Southeast wind 5 to 10 mph. Chance of precipitation is 30%. -Thursday: A chance of rain between 7am and 1pm. High near 62. West wind 5 mph. Chance of precipitation is 30%.""" + long_text = ( + "Tonight: Rain showers likely, mainly before 7am. Low around 52. " + "Southeast wind 5 mph. Chance of precipitation is 80%.\n" + "Tuesday: Rain showers likely, mainly before 7am. High near 64. " + "Southeast wind 5 mph. Chance of precipitation is 60%.\n" + "Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph.\n" + "Wednesday: Partly sunny, with a high near 63. East wind 5 mph.\n" + "Wednesday Night: A chance of rain between 10pm and 1am. Low around 52. " + "Southeast wind 5 to 10 mph. Chance of precipitation is 30%.\n" + "Thursday: A chance of rain between 7am and 1pm. High near 62. " + "West wind 5 mph. Chance of precipitation is 30%." + ) # Split the message parts = split_message(long_text, device_type="zoleo") diff --git a/tests/test_summary_format.py b/tests/test_summary_format.py index 802f275..65e70a8 100644 --- a/tests/test_summary_format.py +++ b/tests/test_summary_format.py @@ -30,11 +30,17 @@ class TestSummaryFormat: @pytest.mark.skipif(not HAS_HA, reason="Home Assistant not available") def test_summary_format_space_after_colon(self): - """Test that summary format includes a space after the colon following day names.""" - sample_forecast = """Tonight: Rain showers likely, mainly before 7am. Low around 52. Southeast wind 5 mph. Chance of precipitation is 80%. -Tuesday: Rain showers likely, mainly before 7am. High near 64. Southeast wind 5 mph. Chance of precipitation is 60%. -Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph. -Wednesday: Partly sunny, with a high near 63. East wind 5 mph.""" + """Test that summary format includes a space after the colon following day + names. + """ + sample_forecast = ( + "Tonight: Rain showers likely, mainly before 7am. Low around 52. " + "Southeast wind 5 mph. Chance of precipitation is 80%.\n" + "Tuesday: Rain showers likely, mainly before 7am. High near 64. " + "Southeast wind 5 mph. Chance of precipitation is 60%.\n" + "Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph.\n" + "Wednesday: Partly sunny, with a high near 63. East wind 5 mph." + ) # Format the summary forecast summary_result = summarize_forecast(sample_forecast) @@ -56,9 +62,13 @@ def test_summary_format_space_after_colon(self): @pytest.mark.skipif(not HAS_HA, reason="Home Assistant not available") def test_summary_format_day_separation(self): """Test that summary format properly separates days with newlines.""" - sample_forecast = """Tonight: Rain showers likely, mainly before 7am. Low around 52. Southeast wind 5 mph. Chance of precipitation is 80%. -Tuesday: Rain showers likely, mainly before 7am. High near 64. Southeast wind 5 mph. Chance of precipitation is 60%. -Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph.""" + sample_forecast = ( + "Tonight: Rain showers likely, mainly before 7am. Low around 52. " + "Southeast wind 5 mph. Chance of precipitation is 80%.\n" + "Tuesday: Rain showers likely, mainly before 7am. High near 64. " + "Southeast wind 5 mph. Chance of precipitation is 60%.\n" + "Tuesday Night: Partly cloudy, with a low around 47. West wind 5 mph." + ) # Format the summary forecast summary_result = summarize_forecast(sample_forecast) @@ -98,8 +108,12 @@ def test_summary_format_day_separation(self): @pytest.mark.skipif(not HAS_HA, reason="Home Assistant not available") def test_summary_format_weather_events(self): """Test that summary format properly includes weather events.""" - sample_forecast = """Tonight: Rain showers likely, mainly before 7am. Low around 52. Southeast wind 5 mph. Chance of precipitation is 80%. -Tuesday: Rain showers likely, mainly before 7am. High near 64. Southeast wind 5 mph. Chance of precipitation is 60%.""" + sample_forecast = ( + "Tonight: Rain showers likely, mainly before 7am. Low around 52. " + "Southeast wind 5 mph. Chance of precipitation is 80%.\n" + "Tuesday: Rain showers likely, mainly before 7am. High near 64. " + "Southeast wind 5 mph. Chance of precipitation is 60%." + ) # Format the summary forecast summary_result = summarize_forecast(sample_forecast) @@ -117,8 +131,12 @@ def test_summary_format_weather_events(self): @pytest.mark.skipif(not HAS_HA, reason="Home Assistant not available") def test_summary_format_no_newlines(self): """Test that summary format uses newlines for separation.""" - sample_forecast = """Tonight: Rain showers likely, mainly before 7am. Low around 52. Southeast wind 5 mph. Chance of precipitation is 80%. -Tuesday: Rain showers likely, mainly before 7am. High near 64. Southeast wind 5 mph. Chance of precipitation is 60%.""" + sample_forecast = ( + "Tonight: Rain showers likely, mainly before 7am. Low around 52. " + "Southeast wind 5 mph. Chance of precipitation is 80%.\n" + "Tuesday: Rain showers likely, mainly before 7am. High near 64. " + "Southeast wind 5 mph. Chance of precipitation is 60%." + ) # Format the summary forecast summary_result = summarize_forecast(sample_forecast) @@ -133,9 +151,15 @@ def test_summary_format_no_newlines(self): @pytest.mark.skipif(not HAS_HA, reason="Home Assistant not available") def test_summary_format_splitting_detection(self): - """Test that summary format is correctly detected by the split_message function.""" - sample_forecast = """Tonight: Rain showers likely, mainly before 7am. Low around 52. Southeast wind 5 mph. Chance of precipitation is 80%. -Tuesday: Rain showers likely, mainly before 7am. High near 64. Southeast wind 5 mph. Chance of precipitation is 60%.""" + """Test that summary format is correctly detected by the split_message + function. + """ + sample_forecast = ( + "Tonight: Rain showers likely, mainly before 7am. Low around 52. " + "Southeast wind 5 mph. Chance of precipitation is 80%.\n" + "Tuesday: Rain showers likely, mainly before 7am. High near 64. " + "Southeast wind 5 mph. Chance of precipitation is 60%." + ) # Format the summary forecast summary_result = summarize_forecast(sample_forecast) diff --git a/tests/test_text_length.py b/tests/test_text_length.py index 28cff45..05b6f42 100644 --- a/tests/test_text_length.py +++ b/tests/test_text_length.py @@ -12,9 +12,15 @@ from forecast_parser import format_forecast # noqa: E402 from split_util import split_message # noqa: E402 -TEST_FORECAST = """This Afternoon: A chance of showers. Mostly cloudy, with a high near 50. Southeast wind around 10 mph. Chance of precipitation is 40%. -Tonight: Showers likely, mainly between 7pm and 10pm, then rain after 10pm. Low around 41. Southeast wind around 15 mph. Chance of precipitation is 80%. -Thursday: Rain likely. Cloudy, with a high near 49. Southeast wind 10 to 15 mph. Chance of precipitation is 70%.""" +TEST_FORECAST = ( + "This Afternoon: A chance of showers. Mostly cloudy, with a high near 50. " + "Southeast wind around 10 mph. Chance of precipitation is 40%.\n" + "Tonight: Showers likely, mainly between 7pm and 10pm, then rain after 10pm. " + "Low around 41. Southeast wind around 15 mph. " + "Chance of precipitation is 80%.\n" + "Thursday: Rain likely. Cloudy, with a high near 49. " + "Southeast wind 10 to 15 mph. Chance of precipitation is 70%." +) class TestTextLength: diff --git a/tests/test_weather_utils_filtering.py b/tests/test_weather_utils_filtering.py index c51ce46..4d2102a 100644 --- a/tests/test_weather_utils_filtering.py +++ b/tests/test_weather_utils_filtering.py @@ -76,7 +76,9 @@ def test_filter_periods_zero(mock_periods): def test_filter_periods_one(mock_periods): - """Test filtering with days=1 (should return Today + Tonight + Tuesday + Tuesday Night).""" + """Test filtering with days=1 (should return Today + Tonight + Tuesday + + Tuesday Night). + """ # days=1 means "Today + Tomorrow" # target_days = 2 result = filter_periods_by_days(mock_periods, 1) diff --git a/tests/verify_installation.py b/tests/verify_installation.py index 7fa1b17..4f3ed57 100644 --- a/tests/verify_installation.py +++ b/tests/verify_installation.py @@ -30,7 +30,8 @@ def check_installation_path(): if not installed_path: print("❌ Integration not found in common locations") print( - "Please ensure you copied the entire 'custom_components/satcom_forecast' folder" + "Please ensure you copied the entire 'custom_components/satcom_forecast' " + "folder" ) return False @@ -163,7 +164,8 @@ def main(): print("1. Restart Home Assistant completely") print("2. Check Home Assistant logs for any error messages") print( - "3. Try searching for 'SatCom Forecast' or 'satcom' in the integration search" + "3. Try searching for 'SatCom Forecast' or 'satcom' in the integration " + "search" ) else: print("\nInstallation structure looks correct.")