| 
1 | 1 | """The test for the calendar_event binary sensor platform."""  | 
2 | 2 | 
 
  | 
3 |  | -from unittest.mock import patch  | 
 | 3 | +from unittest.mock import MagicMock, patch  | 
4 | 4 | 
 
  | 
5 | 5 | import pytest  | 
6 | 6 | from homeassistant.core import HomeAssistant  | 
@@ -193,19 +193,24 @@ async def test_binary_sensor_calendar_unavailable(  | 
193 | 193 |         title="Test Unavailable",  | 
194 | 194 |     )  | 
195 | 195 | 
 
  | 
196 |  | -    await setup_integration(hass, config_entry)  | 
 | 196 | +    with patch(  | 
 | 197 | +        "custom_components.calendar_event.binary_sensor.CalendarEventBinarySensor._get_event_matching_summary"  | 
 | 198 | +    ) as mock_get_events:  | 
 | 199 | +        mock_get_events.return_value = None  | 
197 | 200 | 
 
  | 
198 |  | -    binary_sensor_entity_id = "binary_sensor.test_unavailable"  | 
 | 201 | +        await setup_integration(hass, config_entry)  | 
199 | 202 | 
 
  | 
200 |  | -    # Set calendar to unavailable (remove it from the state registry)  | 
201 |  | -    hass.states.async_remove(mock_calendar_entity.entity_id)  | 
202 |  | -    await hass.async_block_till_done()  | 
 | 203 | +        binary_sensor_entity_id = "binary_sensor.test_unavailable"  | 
 | 204 | + | 
 | 205 | +        # Set calendar to unavailable (remove it from the state registry)  | 
 | 206 | +        hass.states.async_remove(mock_calendar_entity.entity_id)  | 
 | 207 | +        await hass.async_block_till_done()  | 
203 | 208 | 
 
  | 
204 |  | -    # Check binary sensor state  | 
205 |  | -    binary_sensor_state = hass.states.get(binary_sensor_entity_id)  | 
206 |  | -    assert binary_sensor_state is not None  | 
207 |  | -    assert binary_sensor_state.state == "off"  | 
208 |  | -    assert binary_sensor_state.attributes.get(ATTR_DESCRIPTION) == ""  | 
 | 209 | +        # Check binary sensor state  | 
 | 210 | +        binary_sensor_state = hass.states.get(binary_sensor_entity_id)  | 
 | 211 | +        assert binary_sensor_state is not None  | 
 | 212 | +        assert binary_sensor_state.state == "off"  | 
 | 213 | +        assert binary_sensor_state.attributes.get(ATTR_DESCRIPTION) == ""  | 
209 | 214 | 
 
  | 
210 | 215 | 
 
  | 
211 | 216 | async def test_binary_sensor_state_change_listener(  | 
@@ -371,3 +376,195 @@ def test_matches_criteria_logic(  | 
371 | 376 | 
 
  | 
372 | 377 |     result = sensor._matches_criteria(event_summary)  | 
373 | 378 |     assert result == expected_match  | 
 | 379 | + | 
 | 380 | + | 
 | 381 | +async def test_binary_sensor_disabled_no_call_later(  | 
 | 382 | +    hass: HomeAssistant,  | 
 | 383 | +    mock_calendar_entity: er.RegistryEntry,  | 
 | 384 | +) -> None:  | 
 | 385 | +    """Test that disabled binary sensor does not schedule periodic updates."""  | 
 | 386 | + | 
 | 387 | +    config_entry = MockConfigEntry(  | 
 | 388 | +        domain=DOMAIN,  | 
 | 389 | +        data={},  | 
 | 390 | +        options={  | 
 | 391 | +            "name": "Test Disabled",  | 
 | 392 | +            CONF_CALENDAR_ENTITY_ID: mock_calendar_entity.entity_id,  | 
 | 393 | +            CONF_SUMMARY: "meeting",  | 
 | 394 | +            CONF_COMPARISON_METHOD: "contains",  | 
 | 395 | +        },  | 
 | 396 | +        title="Test Disabled",  | 
 | 397 | +    )  | 
 | 398 | + | 
 | 399 | +    with patch(  | 
 | 400 | +        "custom_components.calendar_event.binary_sensor.CalendarEventBinarySensor._get_event_matching_summary"  | 
 | 401 | +    ) as mock_get_events:  | 
 | 402 | +        mock_get_events.return_value = {  | 
 | 403 | +            "summary": "Team Meeting",  | 
 | 404 | +            "description": "Test event description",  | 
 | 405 | +        }  | 
 | 406 | + | 
 | 407 | +        # Mock the call_later method to track if it's called  | 
 | 408 | +        with patch.object(hass.loop, "call_later") as mock_call_later:  | 
 | 409 | +            await setup_integration(hass, config_entry)  | 
 | 410 | + | 
 | 411 | +            binary_sensor_entity_id = "binary_sensor.test_disabled"  | 
 | 412 | + | 
 | 413 | +            # Get the binary sensor entity from the entity registry and disable it  | 
 | 414 | +            entity_registry = er.async_get(hass)  | 
 | 415 | +            entity_entry = entity_registry.async_get(binary_sensor_entity_id)  | 
 | 416 | +            assert entity_entry is not None  | 
 | 417 | + | 
 | 418 | +            # Disable the entity  | 
 | 419 | +            entity_registry.async_update_entity(  | 
 | 420 | +                entity_entry.entity_id, disabled_by=er.RegistryEntryDisabler.USER  | 
 | 421 | +            )  | 
 | 422 | +            await hass.async_block_till_done()  | 
 | 423 | + | 
 | 424 | +            # Reset the mock to clear any previous calls  | 
 | 425 | +            mock_call_later.reset_mock()  | 
 | 426 | + | 
 | 427 | +            # Set calendar to active state to trigger potential call_later  | 
 | 428 | +            hass.states.async_set(  | 
 | 429 | +                mock_calendar_entity.entity_id,  | 
 | 430 | +                "on",  | 
 | 431 | +                {"message": "Team Meeting", "description": "Test description"},  | 
 | 432 | +            )  | 
 | 433 | +            await hass.async_block_till_done()  | 
 | 434 | + | 
 | 435 | +            # Verify that call_later was NOT called for the disabled entity  | 
 | 436 | +            mock_call_later.assert_not_called()  | 
 | 437 | + | 
 | 438 | + | 
 | 439 | +async def test_binary_sensor_enabled_schedules_call_later(  | 
 | 440 | +    hass: HomeAssistant,  | 
 | 441 | +    mock_calendar_entity: er.RegistryEntry,  | 
 | 442 | +) -> None:  | 
 | 443 | +    """Test that enabled binary sensor schedules periodic updates when calendar is on."""  | 
 | 444 | + | 
 | 445 | +    config_entry = MockConfigEntry(  | 
 | 446 | +        domain=DOMAIN,  | 
 | 447 | +        data={},  | 
 | 448 | +        options={  | 
 | 449 | +            "name": "Test Enabled",  | 
 | 450 | +            CONF_CALENDAR_ENTITY_ID: mock_calendar_entity.entity_id,  | 
 | 451 | +            CONF_SUMMARY: "meeting",  | 
 | 452 | +            CONF_COMPARISON_METHOD: "contains",  | 
 | 453 | +        },  | 
 | 454 | +        title="Test Enabled",  | 
 | 455 | +    )  | 
 | 456 | + | 
 | 457 | +    with patch(  | 
 | 458 | +        "custom_components.calendar_event.binary_sensor.CalendarEventBinarySensor._get_event_matching_summary"  | 
 | 459 | +    ) as mock_get_events:  | 
 | 460 | +        mock_get_events.return_value = {  | 
 | 461 | +            "summary": "Team Meeting",  | 
 | 462 | +            "description": "Test event description",  | 
 | 463 | +        }  | 
 | 464 | + | 
 | 465 | +        # Mock the call_later method to track if it's called  | 
 | 466 | +        with patch.object(hass.loop, "call_later") as mock_call_later:  | 
 | 467 | +            await setup_integration(hass, config_entry)  | 
 | 468 | + | 
 | 469 | +            binary_sensor_entity_id = "binary_sensor.test_enabled"  | 
 | 470 | + | 
 | 471 | +            # Verify the entity is enabled by default  | 
 | 472 | +            entity_registry = er.async_get(hass)  | 
 | 473 | +            entity_entry = entity_registry.async_get(binary_sensor_entity_id)  | 
 | 474 | +            assert entity_entry is not None  | 
 | 475 | +            assert not entity_entry.disabled  | 
 | 476 | + | 
 | 477 | +            # Reset the mock to clear any previous calls  | 
 | 478 | +            mock_call_later.reset_mock()  | 
 | 479 | + | 
 | 480 | +            # Set calendar to active state to trigger call_later  | 
 | 481 | +            hass.states.async_set(  | 
 | 482 | +                mock_calendar_entity.entity_id,  | 
 | 483 | +                "on",  | 
 | 484 | +                {"message": "Team Meeting", "description": "Test description"},  | 
 | 485 | +            )  | 
 | 486 | +            await hass.async_block_till_done()  | 
 | 487 | + | 
 | 488 | +            # Verify that call_later WAS called for the enabled entity  | 
 | 489 | +            mock_call_later.assert_called_once()  | 
 | 490 | + | 
 | 491 | +            # Verify the call_later was scheduled with correct parameters  | 
 | 492 | +            call_args = mock_call_later.call_args  | 
 | 493 | +            assert len(call_args[0]) == 2  # delay and callback  | 
 | 494 | +            delay = call_args[0][0]  | 
 | 495 | +            assert isinstance(delay, (int, float))  | 
 | 496 | +            assert 0 < delay <= 60  # Should be between 0 and 60 seconds  | 
 | 497 | + | 
 | 498 | + | 
 | 499 | +async def test_binary_sensor_cancels_call_later_when_disabled(  | 
 | 500 | +    hass: HomeAssistant,  | 
 | 501 | +    mock_calendar_entity: er.RegistryEntry,  | 
 | 502 | +) -> None:  | 
 | 503 | +    """Test that binary sensor cancels call_later when entity is disabled during runtime."""  | 
 | 504 | + | 
 | 505 | +    config_entry = MockConfigEntry(  | 
 | 506 | +        domain=DOMAIN,  | 
 | 507 | +        data={},  | 
 | 508 | +        options={  | 
 | 509 | +            "name": "Test Cancel",  | 
 | 510 | +            CONF_CALENDAR_ENTITY_ID: mock_calendar_entity.entity_id,  | 
 | 511 | +            CONF_SUMMARY: "meeting",  | 
 | 512 | +            CONF_COMPARISON_METHOD: "contains",  | 
 | 513 | +        },  | 
 | 514 | +        title="Test Cancel",  | 
 | 515 | +    )  | 
 | 516 | + | 
 | 517 | +    with patch(  | 
 | 518 | +        "custom_components.calendar_event.binary_sensor.CalendarEventBinarySensor._get_event_matching_summary"  | 
 | 519 | +    ) as mock_get_events:  | 
 | 520 | +        mock_get_events.return_value = {  | 
 | 521 | +            "summary": "Team Meeting",  | 
 | 522 | +            "description": "Test event description",  | 
 | 523 | +        }  | 
 | 524 | + | 
 | 525 | +        await setup_integration(hass, config_entry)  | 
 | 526 | + | 
 | 527 | +        binary_sensor_entity_id = "binary_sensor.test_cancel"  | 
 | 528 | + | 
 | 529 | +        # Get the entity registry  | 
 | 530 | +        entity_registry = er.async_get(hass)  | 
 | 531 | +        entity_entry = entity_registry.async_get(binary_sensor_entity_id)  | 
 | 532 | +        assert entity_entry is not None  | 
 | 533 | + | 
 | 534 | +        # Create a mock handle to track cancellation  | 
 | 535 | +        mock_handle = MagicMock()  | 
 | 536 | + | 
 | 537 | +        with patch.object(  | 
 | 538 | +            hass.loop, "call_later", return_value=mock_handle  | 
 | 539 | +        ) as mock_call_later:  | 
 | 540 | +            # Set calendar to active state to trigger call_later  | 
 | 541 | +            hass.states.async_set(  | 
 | 542 | +                mock_calendar_entity.entity_id,  | 
 | 543 | +                "on",  | 
 | 544 | +                {"message": "Team Meeting", "description": "Test description"},  | 
 | 545 | +            )  | 
 | 546 | +            await hass.async_block_till_done()  | 
 | 547 | + | 
 | 548 | +            # Verify call_later was called and handle was stored  | 
 | 549 | +            mock_call_later.assert_called_once()  | 
 | 550 | + | 
 | 551 | +            # Reset mock to clear call  | 
 | 552 | +            mock_call_later.reset_mock()  | 
 | 553 | + | 
 | 554 | +            # Now disable the entity  | 
 | 555 | +            entity_registry.async_update_entity(  | 
 | 556 | +                entity_entry.entity_id, disabled_by=er.RegistryEntryDisabler.USER  | 
 | 557 | +            )  | 
 | 558 | +            await hass.async_block_till_done()  | 
 | 559 | + | 
 | 560 | +            # Trigger another calendar state change  | 
 | 561 | +            hass.states.async_set(  | 
 | 562 | +                mock_calendar_entity.entity_id,  | 
 | 563 | +                "on",  | 
 | 564 | +                {"message": "Another Meeting", "description": "Test description 2"},  | 
 | 565 | +            )  | 
 | 566 | +            await hass.async_block_till_done()  | 
 | 567 | + | 
 | 568 | +            # Verify the previous handle was cancelled and no new call_later was scheduled  | 
 | 569 | +            mock_handle.cancel.assert_called_once()  | 
 | 570 | +            mock_call_later.assert_not_called()  | 
0 commit comments