test_reauth_flow.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. """Tests for the reauth flows (CalDAV and API modes)."""
  2. from unittest.mock import AsyncMock, patch
  3. import caldav.lib.error as caldav_error
  4. import pytest
  5. from homeassistant.config_entries import SOURCE_REAUTH
  6. from homeassistant.core import HomeAssistant
  7. from pytest_homeassistant_custom_component.common import MockConfigEntry
  8. from custom_components.room_cal_ha.const import (
  9. CONF_API_BASE_URL,
  10. CONF_API_TOKEN,
  11. CONF_CALDAV_URL,
  12. CONF_PASSWORD,
  13. CONF_SYNC_MODE,
  14. CONF_USERNAME,
  15. DOMAIN,
  16. SYNC_MODE_API,
  17. SYNC_MODE_CALDAV,
  18. )
  19. # ---------------------------------------------------------------------------
  20. # Fixtures
  21. # ---------------------------------------------------------------------------
  22. @pytest.fixture
  23. def caldav_entry(hass: HomeAssistant):
  24. entry = MockConfigEntry(
  25. domain=DOMAIN,
  26. data={CONF_USERNAME: "user", CONF_PASSWORD: "pass"},
  27. options={CONF_SYNC_MODE: SYNC_MODE_CALDAV, CONF_CALDAV_URL: "https://cal.example.com"},
  28. )
  29. entry.add_to_hass(hass)
  30. return entry
  31. @pytest.fixture
  32. def api_entry(hass: HomeAssistant):
  33. entry = MockConfigEntry(
  34. domain=DOMAIN,
  35. data={CONF_API_TOKEN: "old-token"},
  36. options={CONF_SYNC_MODE: SYNC_MODE_API, CONF_API_BASE_URL: "https://api.example.com"},
  37. )
  38. entry.add_to_hass(hass)
  39. return entry
  40. def _start_reauth(hass, entry):
  41. return hass.config_entries.flow.async_init(
  42. DOMAIN,
  43. context={"source": SOURCE_REAUTH, "entry_id": entry.entry_id},
  44. data=entry.data,
  45. )
  46. # ---------------------------------------------------------------------------
  47. # CalDAV reauth
  48. # ---------------------------------------------------------------------------
  49. async def test_caldav_reauth_shows_confirm_form(hass: HomeAssistant, caldav_entry):
  50. result = await _start_reauth(hass, caldav_entry)
  51. assert result["type"] == "form"
  52. assert result["step_id"] == "reauth_confirm"
  53. async def test_caldav_reauth_bad_credentials_shows_invalid_auth(hass: HomeAssistant, caldav_entry):
  54. result = await _start_reauth(hass, caldav_entry)
  55. with patch(
  56. "custom_components.room_cal_ha.config_flow._test_caldav_auth",
  57. side_effect=caldav_error.AuthorizationError,
  58. ):
  59. result = await hass.config_entries.flow.async_configure(
  60. result["flow_id"],
  61. user_input={CONF_USERNAME: "user", CONF_PASSWORD: "wrong"},
  62. )
  63. assert result["type"] == "form"
  64. assert result["errors"]["base"] == "invalid_auth"
  65. async def test_caldav_reauth_connect_error_shows_cannot_connect(hass: HomeAssistant, caldav_entry):
  66. result = await _start_reauth(hass, caldav_entry)
  67. with patch(
  68. "custom_components.room_cal_ha.config_flow._test_caldav_auth",
  69. side_effect=Exception("timeout"),
  70. ):
  71. result = await hass.config_entries.flow.async_configure(
  72. result["flow_id"],
  73. user_input={CONF_USERNAME: "user", CONF_PASSWORD: "pass"},
  74. )
  75. assert result["type"] == "form"
  76. assert result["errors"]["base"] == "cannot_connect"
  77. async def test_caldav_reauth_success_updates_credentials(hass: HomeAssistant, caldav_entry):
  78. result = await _start_reauth(hass, caldav_entry)
  79. with (
  80. patch("custom_components.room_cal_ha.config_flow._test_caldav_auth", return_value=None),
  81. patch("custom_components.room_cal_ha.coordinator.CalDavBackend") as MockBackend,
  82. ):
  83. MockBackend.return_value.async_fetch = AsyncMock(return_value=(None, None))
  84. result = await hass.config_entries.flow.async_configure(
  85. result["flow_id"],
  86. user_input={CONF_USERNAME: "new_user", CONF_PASSWORD: "new_pass"},
  87. )
  88. # Drain the event loop so the triggered entry reload completes while patches are active.
  89. await hass.async_block_till_done()
  90. assert result["type"] == "abort"
  91. assert result["reason"] == "reauth_successful"
  92. assert caldav_entry.data[CONF_USERNAME] == "new_user"
  93. assert caldav_entry.data[CONF_PASSWORD] == "new_pass"
  94. # ---------------------------------------------------------------------------
  95. # API reauth
  96. # ---------------------------------------------------------------------------
  97. async def test_api_reauth_shows_api_form(hass: HomeAssistant, api_entry):
  98. result = await _start_reauth(hass, api_entry)
  99. assert result["type"] == "form"
  100. assert result["step_id"] == "reauth_api"
  101. async def test_api_reauth_bad_token_shows_invalid_auth(hass: HomeAssistant, api_entry):
  102. from custom_components.room_cal_ha.config_flow import _ApiAuthError
  103. result = await _start_reauth(hass, api_entry)
  104. with patch(
  105. "custom_components.room_cal_ha.config_flow._test_api_connection",
  106. new=AsyncMock(side_effect=_ApiAuthError),
  107. ):
  108. result = await hass.config_entries.flow.async_configure(
  109. result["flow_id"],
  110. user_input={CONF_API_TOKEN: "bad-token"},
  111. )
  112. assert result["type"] == "form"
  113. assert result["errors"]["base"] == "invalid_auth"
  114. async def test_api_reauth_connect_error_shows_cannot_connect(hass: HomeAssistant, api_entry):
  115. result = await _start_reauth(hass, api_entry)
  116. with patch(
  117. "custom_components.room_cal_ha.config_flow._test_api_connection",
  118. new=AsyncMock(side_effect=Exception("timeout")),
  119. ):
  120. result = await hass.config_entries.flow.async_configure(
  121. result["flow_id"],
  122. user_input={CONF_API_TOKEN: "tok"},
  123. )
  124. assert result["type"] == "form"
  125. assert result["errors"]["base"] == "cannot_connect"
  126. async def test_api_reauth_success_updates_token(hass: HomeAssistant, api_entry):
  127. from custom_components.room_cal_ha.models import ApiStatusData
  128. result = await _start_reauth(hass, api_entry)
  129. with (
  130. patch("custom_components.room_cal_ha.config_flow._test_api_connection", new=AsyncMock(return_value=None)),
  131. patch("custom_components.room_cal_ha.coordinator.RoomCalApiBackend") as MockBackend,
  132. ):
  133. instance = MockBackend.return_value
  134. instance.async_fetch = AsyncMock(return_value=(None, None))
  135. instance.async_fetch_status = AsyncMock(return_value=ApiStatusData(status="ok"))
  136. result = await hass.config_entries.flow.async_configure(
  137. result["flow_id"],
  138. user_input={CONF_API_TOKEN: "fresh-token"},
  139. )
  140. await hass.async_block_till_done()
  141. assert result["type"] == "abort"
  142. assert result["reason"] == "reauth_successful"
  143. assert api_entry.data[CONF_API_TOKEN] == "fresh-token"