async def test_step_discovery(hass, flow_handler, local_impl): """Check flow triggers from discovery.""" await async_process_ha_core_config( hass, {"external_url": "https://example.com"}, ) flow_handler.async_register_implementation(hass, local_impl) config_entry_oauth2_flow.async_register_implementation( hass, TEST_DOMAIN, MockOAuth2Implementation()) result = await hass.config_entries.flow.async_init( TEST_DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "pick_implementation"
async def test_abort_if_oauth_rejected( hass, flow_handler, local_impl, hass_client_no_auth, aioclient_mock, current_request_with_host, ): """Check bad oauth token.""" flow_handler.async_register_implementation(hass, local_impl) config_entry_oauth2_flow.async_register_implementation( hass, TEST_DOMAIN, MockOAuth2Implementation()) result = await hass.config_entries.flow.async_init( TEST_DOMAIN, context={"source": config_entries.SOURCE_USER}) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "pick_implementation" # Pick implementation result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={"implementation": TEST_DOMAIN}) state = config_entry_oauth2_flow._encode_jwt( hass, { "flow_id": result["flow_id"], "redirect_uri": "https://example.com/auth/external/callback", }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP assert result["url"] == ( f"{AUTHORIZE_URL}?response_type=code&client_id={CLIENT_ID}" "&redirect_uri=https://example.com/auth/external/callback" f"&state={state}&scope=read+write") client = await hass_client_no_auth() resp = await client.get( f"/auth/external/callback?error=access_denied&state={state}") assert resp.status == 200 assert resp.headers["content-type"] == "text/html; charset=utf-8" result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "user_rejected_authorize" assert result["description_placeholders"] == {"error": "access_denied"}
async def test_abort_discovered_multiple(hass, flow_handler, local_impl): """Test if aborts when discovered multiple times.""" flow_handler.async_register_implementation(hass, local_impl) config_entry_oauth2_flow.async_register_implementation( hass, TEST_DOMAIN, MockOAuth2Implementation()) result = await hass.config_entries.flow.async_init( TEST_DOMAIN, context={"source": config_entries.SOURCE_SSDP}) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "pick_implementation" result = await hass.config_entries.flow.async_init( TEST_DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_in_progress"
async def test_abort_discovered_existing_entries(hass, flow_handler, local_impl): """Test if abort discovery when entries exists.""" hass.config.api.base_url = "https://example.com" flow_handler.async_register_implementation(hass, local_impl) config_entry_oauth2_flow.async_register_implementation( hass, TEST_DOMAIN, MockOAuth2Implementation() ) entry = MockConfigEntry(domain=TEST_DOMAIN, data={},) entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( TEST_DOMAIN, context={"source": config_entries.SOURCE_SSDP} ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_configured"
async def test_wrong_configuration(hass: HomeAssistant, ) -> None: """Test can't use the wrong type of authentication.""" # Google calendar flow currently only supports device auth config_entry_oauth2_flow.async_register_implementation( hass, DOMAIN, config_entry_oauth2_flow.LocalOAuth2Implementation( hass, DOMAIN, CLIENT_ID, CLIENT_SECRET, "http://example/authorize", "http://example/token", ), ) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}) assert result.get("type") == "abort" assert result.get("reason") == "oauth_error"
async def test_implementation_provider(hass, local_impl): """Test providing an implementation provider.""" assert ( await config_entry_oauth2_flow.async_get_implementations(hass, TEST_DOMAIN) == {} ) mock_domain_with_impl = "some_domain" config_entry_oauth2_flow.async_register_implementation( hass, mock_domain_with_impl, local_impl ) assert await config_entry_oauth2_flow.async_get_implementations( hass, mock_domain_with_impl ) == {TEST_DOMAIN: local_impl} provider_source = {} async def async_provide_implementation(hass, domain): """Mock implementation provider.""" return provider_source.get(domain) config_entry_oauth2_flow.async_add_implementation_provider( hass, "cloud", async_provide_implementation ) assert await config_entry_oauth2_flow.async_get_implementations( hass, mock_domain_with_impl ) == {TEST_DOMAIN: local_impl} provider_source[ mock_domain_with_impl ] = config_entry_oauth2_flow.LocalOAuth2Implementation( hass, "cloud", CLIENT_ID, CLIENT_SECRET, AUTHORIZE_URL, TOKEN_URL ) assert await config_entry_oauth2_flow.async_get_implementations( hass, mock_domain_with_impl ) == {TEST_DOMAIN: local_impl, "cloud": provider_source[mock_domain_with_impl]}
async def async_step_get_oauth_info(self, user_input=None): """Ask user to provide Strava API Credentials""" data_schema = { vol.Required(CONF_CLIENT_ID): str, vol.Required(CONF_CLIENT_SECRET): str, vol.Required(CONF_PHOTOS, default=self._import_photos_from_strava): bool, } assert self.hass is not None if self.hass.config_entries.async_entries(self.DOMAIN): return self.async_abort(reason="already_configured") try: get_url(self.hass, allow_internal=False, allow_ip=False) except NoURLAvailableError: return self.async_abort(reason="no_public_url") if user_input is not None: self._import_photos_from_strava = user_input[CONF_PHOTOS] config_entry_oauth2_flow.async_register_implementation( self.hass, DOMAIN, config_entry_oauth2_flow.LocalOAuth2Implementation( self.hass, DOMAIN, user_input[CONF_CLIENT_ID], user_input[CONF_CLIENT_SECRET], OAUTH2_AUTHORIZE, OAUTH2_TOKEN, ), ) return await self.async_step_pick_implementation() return self.async_show_form(step_id="get_oauth_info", data_schema=vol.Schema(data_schema))
async def test_full_flow( hass, flow_handler, local_impl, hass_client_no_auth, aioclient_mock, current_request_with_host, ): """Check full flow.""" flow_handler.async_register_implementation(hass, local_impl) config_entry_oauth2_flow.async_register_implementation( hass, TEST_DOMAIN, MockOAuth2Implementation()) result = await hass.config_entries.flow.async_init( TEST_DOMAIN, context={"source": config_entries.SOURCE_USER}) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "pick_implementation" # Pick implementation result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={"implementation": TEST_DOMAIN}) state = config_entry_oauth2_flow._encode_jwt( hass, { "flow_id": result["flow_id"], "redirect_uri": "https://example.com/auth/external/callback", }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP assert result["url"] == ( f"{AUTHORIZE_URL}?response_type=code&client_id={CLIENT_ID}" "&redirect_uri=https://example.com/auth/external/callback" f"&state={state}&scope=read+write") client = await hass_client_no_auth() resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") assert resp.status == 200 assert resp.headers["content-type"] == "text/html; charset=utf-8" aioclient_mock.post( TOKEN_URL, json={ "refresh_token": REFRESH_TOKEN, "access_token": ACCESS_TOKEN_1, "type": "bearer", "expires_in": 60, }, ) result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["data"]["auth_implementation"] == TEST_DOMAIN result["data"]["token"].pop("expires_at") assert result["data"]["token"] == { "refresh_token": REFRESH_TOKEN, "access_token": ACCESS_TOKEN_1, "type": "bearer", "expires_in": 60, } entry = hass.config_entries.async_entries(TEST_DOMAIN)[0] assert (await config_entry_oauth2_flow.async_get_config_entry_implementation( hass, entry) is local_impl)