mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
Improve OAuth error handling in configuration flows (#103157)
* Improve OAuth error handling in configuration flows * Update strings for all integrations that use oauth2 config flow * Remove invalid_auth strings * Revert change to release * Revert close change in aiohttp mock
This commit is contained in:
parent
667a453a35
commit
787fb3b954
@ -17,7 +17,10 @@
|
|||||||
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
||||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
||||||
"user_rejected_authorize": "[%key:common::config_flow::abort::oauth2_user_rejected_authorize%]",
|
"user_rejected_authorize": "[%key:common::config_flow::abort::oauth2_user_rejected_authorize%]",
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||||
@ -25,17 +28,9 @@
|
|||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"hopfreepowerstart": {
|
"hopfreepowerstart": { "name": "Hour of free power start" },
|
||||||
"name": "Hour of free power start"
|
"hopfreepowerend": { "name": "Hour of free power end" }
|
||||||
},
|
|
||||||
"hopfreepowerend": {
|
|
||||||
"name": "Hour of free power end"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"select": {
|
"select": { "hopselector": { "name": "Hour of free power" } }
|
||||||
"hopselector": {
|
|
||||||
"name": "Hour of free power"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,10 @@
|
|||||||
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]",
|
"unknown": "[%key:common::config_flow::error::unknown%]",
|
||||||
"wrong_account": "The user credentials provided do not match this Fitbit account."
|
"wrong_account": "The user credentials provided do not match this Fitbit account.",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||||
|
@ -16,7 +16,10 @@
|
|||||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||||
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
||||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||||
@ -24,21 +27,11 @@
|
|||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"find_count": {
|
"find_count": { "name": "Total finds" },
|
||||||
"name": "Total finds"
|
"hide_count": { "name": "Total hides" },
|
||||||
},
|
"favorite_points": { "name": "Favorite points" },
|
||||||
"hide_count": {
|
"souvenir_count": { "name": "Total souvenirs" },
|
||||||
"name": "Total hides"
|
"awarded_favorite_points": { "name": "Awarded favorite points" }
|
||||||
},
|
|
||||||
"favorite_points": {
|
|
||||||
"name": "Favorite points"
|
|
||||||
},
|
|
||||||
"souvenir_count": {
|
|
||||||
"name": "Total souvenirs"
|
|
||||||
},
|
|
||||||
"awarded_favorite_points": {
|
|
||||||
"name": "Awarded favorite points"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,10 @@
|
|||||||
"code_expired": "Authentication code expired or credential setup is invalid, please try again.",
|
"code_expired": "Authentication code expired or credential setup is invalid, please try again.",
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
"invalid_access_token": "[%key:common::config_flow::error::invalid_access_token%]",
|
"invalid_access_token": "[%key:common::config_flow::error::invalid_access_token%]",
|
||||||
"api_disabled": "You must enable the Google Calendar API in the Google Cloud Console"
|
"api_disabled": "You must enable the Google Calendar API in the Google Cloud Console",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||||
|
@ -21,7 +21,10 @@
|
|||||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
"invalid_access_token": "[%key:common::config_flow::error::invalid_access_token%]",
|
"invalid_access_token": "[%key:common::config_flow::error::invalid_access_token%]",
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
"unknown": "[%key:common::config_flow::error::unknown%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||||
|
@ -22,7 +22,10 @@
|
|||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
"invalid_access_token": "[%key:common::config_flow::error::invalid_access_token%]",
|
"invalid_access_token": "[%key:common::config_flow::error::invalid_access_token%]",
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]",
|
"unknown": "[%key:common::config_flow::error::unknown%]",
|
||||||
"wrong_account": "Wrong account: Please authenticate with {email}."
|
"wrong_account": "Wrong account: Please authenticate with {email}.",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||||
|
@ -4,9 +4,7 @@
|
|||||||
"pick_implementation": {
|
"pick_implementation": {
|
||||||
"title": "[%key:common::config_flow::title::oauth2_pick_implementation%]"
|
"title": "[%key:common::config_flow::title::oauth2_pick_implementation%]"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": { "title": "Link Google Account" },
|
||||||
"title": "Link Google Account"
|
|
||||||
},
|
|
||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
"title": "[%key:common::config_flow::title::reauth%]",
|
"title": "[%key:common::config_flow::title::reauth%]",
|
||||||
"description": "The Google Sheets integration needs to re-authenticate your account"
|
"description": "The Google Sheets integration needs to re-authenticate your account"
|
||||||
@ -23,7 +21,10 @@
|
|||||||
"invalid_access_token": "[%key:common::config_flow::error::invalid_access_token%]",
|
"invalid_access_token": "[%key:common::config_flow::error::invalid_access_token%]",
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]",
|
"unknown": "[%key:common::config_flow::error::unknown%]",
|
||||||
"create_spreadsheet_failure": "Error while creating spreadsheet, see error log for details",
|
"create_spreadsheet_failure": "Error while creating spreadsheet, see error log for details",
|
||||||
"open_spreadsheet_failure": "Error while opening spreadsheet, see error log for details"
|
"open_spreadsheet_failure": "Error while opening spreadsheet, see error log for details",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "Successfully authenticated and spreadsheet created at: {url}"
|
"default": "Successfully authenticated and spreadsheet created at: {url}"
|
||||||
|
@ -17,7 +17,10 @@
|
|||||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
||||||
"user_rejected_authorize": "[%key:common::config_flow::abort::oauth2_user_rejected_authorize%]",
|
"user_rejected_authorize": "[%key:common::config_flow::abort::oauth2_user_rejected_authorize%]",
|
||||||
"access_not_configured": "Unable to access the Google API:\n\n{message}",
|
"access_not_configured": "Unable to access the Google API:\n\n{message}",
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
"unknown": "[%key:common::config_flow::error::unknown%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||||
|
@ -7,7 +7,11 @@
|
|||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]"
|
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
||||||
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||||
@ -22,22 +26,13 @@
|
|||||||
"name": "Device ID",
|
"name": "Device ID",
|
||||||
"description": "Id of the device."
|
"description": "Id of the device."
|
||||||
},
|
},
|
||||||
"program": {
|
"program": { "name": "Program", "description": "Program to select." },
|
||||||
"name": "Program",
|
"key": { "name": "Option key", "description": "Key of the option." },
|
||||||
"description": "Program to select."
|
|
||||||
},
|
|
||||||
"key": {
|
|
||||||
"name": "Option key",
|
|
||||||
"description": "Key of the option."
|
|
||||||
},
|
|
||||||
"value": {
|
"value": {
|
||||||
"name": "Option value",
|
"name": "Option value",
|
||||||
"description": "Value of the option."
|
"description": "Value of the option."
|
||||||
},
|
},
|
||||||
"unit": {
|
"unit": { "name": "Option unit", "description": "Unit for the option." }
|
||||||
"name": "Option unit",
|
|
||||||
"description": "Unit for the option."
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"select_program": {
|
"select_program": {
|
||||||
@ -130,14 +125,8 @@
|
|||||||
"name": "Device ID",
|
"name": "Device ID",
|
||||||
"description": "[%key:component::home_connect::services::start_program::fields::device_id::description%]"
|
"description": "[%key:component::home_connect::services::start_program::fields::device_id::description%]"
|
||||||
},
|
},
|
||||||
"key": {
|
"key": { "name": "Key", "description": "Key of the setting." },
|
||||||
"name": "Key",
|
"value": { "name": "Value", "description": "Value of the setting." }
|
||||||
"description": "Key of the setting."
|
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
"name": "Value",
|
|
||||||
"description": "Value of the setting."
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,11 @@
|
|||||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||||
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
||||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
||||||
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
|
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
|
||||||
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||||
|
@ -41,7 +41,11 @@
|
|||||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
||||||
"reauth_device_not_found": "The device you are trying to re-authenticate is not found in this LaMetric account",
|
"reauth_device_not_found": "The device you are trying to re-authenticate is not found in this LaMetric account",
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
"unknown": "[%key:common::config_flow::error::unknown%]",
|
||||||
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
|
@ -12,7 +12,11 @@
|
|||||||
"abort": {
|
"abort": {
|
||||||
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
||||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||||
|
@ -13,7 +13,11 @@
|
|||||||
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
||||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||||
|
@ -49,7 +49,11 @@
|
|||||||
"unknown_authorize_url_generation": "[%key:common::config_flow::abort::unknown_authorize_url_generation%]",
|
"unknown_authorize_url_generation": "[%key:common::config_flow::abort::unknown_authorize_url_generation%]",
|
||||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
"invalid_access_token": "[%key:common::config_flow::error::invalid_access_token%]"
|
"invalid_access_token": "[%key:common::config_flow::error::invalid_access_token%]",
|
||||||
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||||
|
@ -14,7 +14,11 @@
|
|||||||
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
||||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||||
|
@ -7,7 +7,11 @@
|
|||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||||
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]"
|
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
||||||
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||||
|
@ -11,7 +11,10 @@
|
|||||||
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||||
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
||||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]"
|
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||||
|
@ -29,7 +29,11 @@
|
|||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||||
"invalid_mdns": "Unsupported device for the Smappee integration.",
|
"invalid_mdns": "Unsupported device for the Smappee integration.",
|
||||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]"
|
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
||||||
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,11 @@
|
|||||||
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
||||||
"missing_configuration": "The Spotify integration is not configured. Please follow the documentation.",
|
"missing_configuration": "The Spotify integration is not configured. Please follow the documentation.",
|
||||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
||||||
"reauth_account_mismatch": "The Spotify account authenticated with, does not match the account needed re-authentication."
|
"reauth_account_mismatch": "The Spotify account authenticated with, does not match the account needed re-authentication.",
|
||||||
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "Successfully authenticated with Spotify."
|
"default": "Successfully authenticated with Spotify."
|
||||||
|
@ -18,7 +18,11 @@
|
|||||||
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
||||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||||
"no_agreements": "This account has no Toon displays.",
|
"no_agreements": "This account has no Toon displays.",
|
||||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]"
|
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
||||||
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
|
@ -10,7 +10,11 @@
|
|||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]",
|
"unknown": "[%key:common::config_flow::error::unknown%]",
|
||||||
"wrong_account": "Wrong account: Please authenticate with {username}."
|
"wrong_account": "Wrong account: Please authenticate with {username}.",
|
||||||
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"issues": {
|
"issues": {
|
||||||
|
@ -17,7 +17,10 @@
|
|||||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||||
"already_configured": "Configuration updated for profile.",
|
"already_configured": "Configuration updated for profile.",
|
||||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
||||||
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]"
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "Successfully authenticated with Withings."
|
"default": "Successfully authenticated with Withings."
|
||||||
|
@ -8,7 +8,11 @@
|
|||||||
"abort": {
|
"abort": {
|
||||||
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
|
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
|
||||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||||
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]"
|
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
||||||
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||||
|
@ -16,7 +16,10 @@
|
|||||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||||
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
||||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||||
@ -36,21 +39,11 @@
|
|||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
"switch": {
|
"switch": {
|
||||||
"usb_ports": {
|
"usb_ports": { "name": "USB ports" },
|
||||||
"name": "USB ports"
|
"plug_1": { "name": "Plug 1" },
|
||||||
},
|
"plug_2": { "name": "Plug 2" },
|
||||||
"plug_1": {
|
"plug_3": { "name": "Plug 3" },
|
||||||
"name": "Plug 1"
|
"plug_4": { "name": "Plug 4" }
|
||||||
},
|
|
||||||
"plug_2": {
|
|
||||||
"name": "Plug 2"
|
|
||||||
},
|
|
||||||
"plug_3": {
|
|
||||||
"name": "Plug 3"
|
|
||||||
},
|
|
||||||
"plug_4": {
|
|
||||||
"name": "Plug 4"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"power_failure_alarm": {
|
"power_failure_alarm": {
|
||||||
@ -63,18 +56,11 @@
|
|||||||
},
|
},
|
||||||
"power_failure_alarm_mute": {
|
"power_failure_alarm_mute": {
|
||||||
"name": "Power failure alarm mute",
|
"name": "Power failure alarm mute",
|
||||||
"state": {
|
"state": { "muted": "Muted", "unmuted": "Unmuted" }
|
||||||
"muted": "Muted",
|
|
||||||
"unmuted": "Unmuted"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"power_failure_alarm_volume": {
|
"power_failure_alarm_volume": {
|
||||||
"name": "Power failure alarm volume",
|
"name": "Power failure alarm volume",
|
||||||
"state": {
|
"state": { "low": "Low", "medium": "Medium", "high": "High" }
|
||||||
"low": "Low",
|
|
||||||
"medium": "Medium",
|
|
||||||
"high": "High"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"power_failure_alarm_beep": {
|
"power_failure_alarm_beep": {
|
||||||
"name": "Power failure alarm beep",
|
"name": "Power failure alarm beep",
|
||||||
|
@ -6,7 +6,11 @@
|
|||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
"no_subscriptions": "You need to be subscribed to YouTube channels in order to add them.",
|
"no_subscriptions": "You need to be subscribed to YouTube channels in order to add them.",
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
"unknown": "[%key:common::config_flow::error::unknown%]",
|
||||||
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
@ -15,9 +19,7 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"channels": {
|
"channels": {
|
||||||
"description": "Select the channels you want to add.",
|
"description": "Select the channels you want to add.",
|
||||||
"data": {
|
"data": { "channels": "YouTube channels" }
|
||||||
"channels": "YouTube channels"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
"title": "[%key:common::config_flow::title::reauth%]",
|
"title": "[%key:common::config_flow::title::reauth%]",
|
||||||
@ -40,17 +42,11 @@
|
|||||||
"latest_upload": {
|
"latest_upload": {
|
||||||
"name": "Latest upload",
|
"name": "Latest upload",
|
||||||
"state_attributes": {
|
"state_attributes": {
|
||||||
"video_id": {
|
"video_id": { "name": "Video ID" },
|
||||||
"name": "Video ID"
|
"published_at": { "name": "Published at" }
|
||||||
},
|
|
||||||
"published_at": {
|
|
||||||
"name": "Published at"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"subscribers": {
|
"subscribers": { "name": "Subscribers" }
|
||||||
"name": "Subscribers"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,12 +10,14 @@ from __future__ import annotations
|
|||||||
from abc import ABC, ABCMeta, abstractmethod
|
from abc import ABC, ABCMeta, abstractmethod
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
|
from http import HTTPStatus
|
||||||
|
from json import JSONDecodeError
|
||||||
import logging
|
import logging
|
||||||
import secrets
|
import secrets
|
||||||
import time
|
import time
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from aiohttp import client, web
|
from aiohttp import ClientError, ClientResponseError, client, web
|
||||||
import jwt
|
import jwt
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from yarl import URL
|
from yarl import URL
|
||||||
@ -199,12 +201,15 @@ class LocalOAuth2Implementation(AbstractOAuth2Implementation):
|
|||||||
|
|
||||||
_LOGGER.debug("Sending token request to %s", self.token_url)
|
_LOGGER.debug("Sending token request to %s", self.token_url)
|
||||||
resp = await session.post(self.token_url, data=data)
|
resp = await session.post(self.token_url, data=data)
|
||||||
if resp.status >= 400 and _LOGGER.isEnabledFor(logging.DEBUG):
|
if resp.status >= 400:
|
||||||
body = await resp.text()
|
try:
|
||||||
_LOGGER.debug(
|
error_response = await resp.json()
|
||||||
"Token request failed with status=%s, body=%s",
|
except (ClientError, JSONDecodeError):
|
||||||
resp.status,
|
error_response = {}
|
||||||
body,
|
error_code = error_response.get("error", "unknown")
|
||||||
|
error_description = error_response.get("error_description", "unknown error")
|
||||||
|
_LOGGER.error(
|
||||||
|
"Token request failed (%s): %s", error_code, error_description
|
||||||
)
|
)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
return cast(dict, await resp.json())
|
return cast(dict, await resp.json())
|
||||||
@ -317,7 +322,14 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta):
|
|||||||
)
|
)
|
||||||
except asyncio.TimeoutError as err:
|
except asyncio.TimeoutError as err:
|
||||||
_LOGGER.error("Timeout resolving OAuth token: %s", err)
|
_LOGGER.error("Timeout resolving OAuth token: %s", err)
|
||||||
return self.async_abort(reason="oauth2_timeout")
|
return self.async_abort(reason="oauth_timeout")
|
||||||
|
except (ClientResponseError, ClientError) as err:
|
||||||
|
if (
|
||||||
|
isinstance(err, ClientResponseError)
|
||||||
|
and err.status == HTTPStatus.UNAUTHORIZED
|
||||||
|
):
|
||||||
|
return self.async_abort(reason="oauth_unauthorized")
|
||||||
|
return self.async_abort(reason="oauth_failed")
|
||||||
|
|
||||||
if "expires_in" not in token:
|
if "expires_in" not in token:
|
||||||
_LOGGER.warning("Invalid token: %s", token)
|
_LOGGER.warning("Invalid token: %s", token)
|
||||||
|
@ -126,6 +126,8 @@
|
|||||||
"oauth2_authorize_url_timeout": "Timeout generating authorize URL.",
|
"oauth2_authorize_url_timeout": "Timeout generating authorize URL.",
|
||||||
"oauth2_no_url_available": "No URL available. For information about this error, [check the help section]({docs_url})",
|
"oauth2_no_url_available": "No URL available. For information about this error, [check the help section]({docs_url})",
|
||||||
"oauth2_user_rejected_authorize": "Account linking rejected: {error}",
|
"oauth2_user_rejected_authorize": "Account linking rejected: {error}",
|
||||||
|
"oauth2_unauthorized": "OAuth authorization error while obtaining access token.",
|
||||||
|
"oauth2_failed": "Error while obtaining access token.",
|
||||||
"reauth_successful": "Re-authentication was successful",
|
"reauth_successful": "Re-authentication was successful",
|
||||||
"unknown_authorize_url_generation": "Unknown error generating an authorize URL.",
|
"unknown_authorize_url_generation": "Unknown error generating an authorize URL.",
|
||||||
"cloud_not_connected": "Not connected to Home Assistant Cloud."
|
"cloud_not_connected": "Not connected to Home Assistant Cloud."
|
||||||
|
@ -185,6 +185,9 @@ def _custom_tasks(template, info: Info) -> None:
|
|||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
||||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||||
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed]",
|
||||||
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized]",
|
||||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||||
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
||||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""Test the Google Nest Device Access config flow."""
|
"""Test the Google Nest Device Access config flow."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from http import HTTPStatus
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
@ -27,7 +28,9 @@ from .common import (
|
|||||||
SUBSCRIBER_ID,
|
SUBSCRIBER_ID,
|
||||||
TEST_CONFIG_APP_CREDS,
|
TEST_CONFIG_APP_CREDS,
|
||||||
TEST_CONFIGFLOW_APP_CREDS,
|
TEST_CONFIGFLOW_APP_CREDS,
|
||||||
|
FakeSubscriber,
|
||||||
NestTestConfig,
|
NestTestConfig,
|
||||||
|
PlatformSetup,
|
||||||
)
|
)
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
@ -92,8 +95,6 @@ class OAuthFixture:
|
|||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
assert resp.headers["content-type"] == "text/html; charset=utf-8"
|
assert resp.headers["content-type"] == "text/html; charset=utf-8"
|
||||||
|
|
||||||
await self.async_mock_refresh(result)
|
|
||||||
|
|
||||||
async def async_reauth(self, config_entry: ConfigEntry) -> dict:
|
async def async_reauth(self, config_entry: ConfigEntry) -> dict:
|
||||||
"""Initiate a reuath flow."""
|
"""Initiate a reuath flow."""
|
||||||
config_entry.async_start_reauth(self.hass)
|
config_entry.async_start_reauth(self.hass)
|
||||||
@ -137,7 +138,7 @@ class OAuthFixture:
|
|||||||
"&access_type=offline&prompt=consent"
|
"&access_type=offline&prompt=consent"
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_mock_refresh(self, result, user_input: dict = None) -> None:
|
def async_mock_refresh(self) -> None:
|
||||||
"""Finish the OAuth flow exchanging auth token for refresh token."""
|
"""Finish the OAuth flow exchanging auth token for refresh token."""
|
||||||
self.aioclient_mock.post(
|
self.aioclient_mock.post(
|
||||||
OAUTH2_TOKEN,
|
OAUTH2_TOKEN,
|
||||||
@ -202,6 +203,7 @@ async def test_app_credentials(
|
|||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
await oauth.async_app_creds_flow(result)
|
await oauth.async_app_creds_flow(result)
|
||||||
|
oauth.async_mock_refresh()
|
||||||
|
|
||||||
entry = await oauth.async_finish_setup(result)
|
entry = await oauth.async_finish_setup(result)
|
||||||
|
|
||||||
@ -235,6 +237,7 @@ async def test_config_flow_restart(
|
|||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
await oauth.async_app_creds_flow(result)
|
await oauth.async_app_creds_flow(result)
|
||||||
|
oauth.async_mock_refresh()
|
||||||
|
|
||||||
# At this point, we should have a valid auth implementation configured.
|
# At this point, we should have a valid auth implementation configured.
|
||||||
# Simulate aborting the flow and starting over to ensure we get prompted
|
# Simulate aborting the flow and starting over to ensure we get prompted
|
||||||
@ -254,6 +257,7 @@ async def test_config_flow_restart(
|
|||||||
|
|
||||||
result = await oauth.async_configure(result, {"project_id": "new-project-id"})
|
result = await oauth.async_configure(result, {"project_id": "new-project-id"})
|
||||||
await oauth.async_oauth_web_flow(result, "new-project-id")
|
await oauth.async_oauth_web_flow(result, "new-project-id")
|
||||||
|
oauth.async_mock_refresh()
|
||||||
|
|
||||||
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
||||||
|
|
||||||
@ -305,6 +309,7 @@ async def test_config_flow_wrong_project_id(
|
|||||||
result = await oauth.async_configure(result, {"project_id": PROJECT_ID})
|
result = await oauth.async_configure(result, {"project_id": PROJECT_ID})
|
||||||
await oauth.async_oauth_web_flow(result)
|
await oauth.async_oauth_web_flow(result)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
oauth.async_mock_refresh()
|
||||||
|
|
||||||
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
||||||
|
|
||||||
@ -341,6 +346,7 @@ async def test_config_flow_pubsub_configuration_error(
|
|||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
await oauth.async_app_creds_flow(result)
|
await oauth.async_app_creds_flow(result)
|
||||||
|
oauth.async_mock_refresh()
|
||||||
|
|
||||||
mock_subscriber.create_subscription.side_effect = ConfigurationException
|
mock_subscriber.create_subscription.side_effect = ConfigurationException
|
||||||
result = await oauth.async_configure(result, {"code": "1234"})
|
result = await oauth.async_configure(result, {"code": "1234"})
|
||||||
@ -360,6 +366,7 @@ async def test_config_flow_pubsub_subscriber_error(
|
|||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
await oauth.async_app_creds_flow(result)
|
await oauth.async_app_creds_flow(result)
|
||||||
|
oauth.async_mock_refresh()
|
||||||
|
|
||||||
mock_subscriber.create_subscription.side_effect = SubscriberException()
|
mock_subscriber.create_subscription.side_effect = SubscriberException()
|
||||||
result = await oauth.async_configure(result, {"code": "1234"})
|
result = await oauth.async_configure(result, {"code": "1234"})
|
||||||
@ -384,6 +391,7 @@ async def test_multiple_config_entries(
|
|||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
await oauth.async_app_creds_flow(result, project_id="project-id-2")
|
await oauth.async_app_creds_flow(result, project_id="project-id-2")
|
||||||
|
oauth.async_mock_refresh()
|
||||||
entry = await oauth.async_finish_setup(result)
|
entry = await oauth.async_finish_setup(result)
|
||||||
assert entry.title == "Mock Title"
|
assert entry.title == "Mock Title"
|
||||||
assert "token" in entry.data
|
assert "token" in entry.data
|
||||||
@ -442,6 +450,7 @@ async def test_reauth_multiple_config_entries(
|
|||||||
result = await oauth.async_reauth(config_entry)
|
result = await oauth.async_reauth(config_entry)
|
||||||
|
|
||||||
await oauth.async_oauth_web_flow(result)
|
await oauth.async_oauth_web_flow(result)
|
||||||
|
oauth.async_mock_refresh()
|
||||||
|
|
||||||
await oauth.async_finish_setup(result)
|
await oauth.async_finish_setup(result)
|
||||||
|
|
||||||
@ -479,6 +488,7 @@ async def test_pubsub_subscription_strip_whitespace(
|
|||||||
await oauth.async_app_creds_flow(
|
await oauth.async_app_creds_flow(
|
||||||
result, cloud_project_id=" " + CLOUD_PROJECT_ID + " "
|
result, cloud_project_id=" " + CLOUD_PROJECT_ID + " "
|
||||||
)
|
)
|
||||||
|
oauth.async_mock_refresh()
|
||||||
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
||||||
|
|
||||||
assert entry.title == "Import from configuration.yaml"
|
assert entry.title == "Import from configuration.yaml"
|
||||||
@ -508,6 +518,7 @@ async def test_pubsub_subscription_auth_failure(
|
|||||||
mock_subscriber.create_subscription.side_effect = AuthException()
|
mock_subscriber.create_subscription.side_effect = AuthException()
|
||||||
|
|
||||||
await oauth.async_app_creds_flow(result)
|
await oauth.async_app_creds_flow(result)
|
||||||
|
oauth.async_mock_refresh()
|
||||||
result = await oauth.async_configure(result, {"code": "1234"})
|
result = await oauth.async_configure(result, {"code": "1234"})
|
||||||
|
|
||||||
assert result["type"] == "abort"
|
assert result["type"] == "abort"
|
||||||
@ -527,6 +538,7 @@ async def test_pubsub_subscriber_config_entry_reauth(
|
|||||||
|
|
||||||
result = await oauth.async_reauth(config_entry)
|
result = await oauth.async_reauth(config_entry)
|
||||||
await oauth.async_oauth_web_flow(result)
|
await oauth.async_oauth_web_flow(result)
|
||||||
|
oauth.async_mock_refresh()
|
||||||
|
|
||||||
# Entering an updated access token refreshes the config entry.
|
# Entering an updated access token refreshes the config entry.
|
||||||
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
||||||
@ -568,6 +580,7 @@ async def test_config_entry_title_from_home(
|
|||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
await oauth.async_app_creds_flow(result)
|
await oauth.async_app_creds_flow(result)
|
||||||
|
oauth.async_mock_refresh()
|
||||||
|
|
||||||
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
||||||
assert entry.title == "Example Home"
|
assert entry.title == "Example Home"
|
||||||
@ -613,6 +626,7 @@ async def test_config_entry_title_multiple_homes(
|
|||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
await oauth.async_app_creds_flow(result)
|
await oauth.async_app_creds_flow(result)
|
||||||
|
oauth.async_mock_refresh()
|
||||||
|
|
||||||
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
||||||
assert entry.title == "Example Home #1, Example Home #2"
|
assert entry.title == "Example Home #1, Example Home #2"
|
||||||
@ -628,6 +642,7 @@ async def test_title_failure_fallback(
|
|||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
await oauth.async_app_creds_flow(result)
|
await oauth.async_app_creds_flow(result)
|
||||||
|
oauth.async_mock_refresh()
|
||||||
|
|
||||||
mock_subscriber.async_get_device_manager.side_effect = AuthException()
|
mock_subscriber.async_get_device_manager.side_effect = AuthException()
|
||||||
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
||||||
@ -659,6 +674,7 @@ async def test_structure_missing_trait(
|
|||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
await oauth.async_app_creds_flow(result)
|
await oauth.async_app_creds_flow(result)
|
||||||
|
oauth.async_mock_refresh()
|
||||||
|
|
||||||
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
||||||
# Fallback to default name
|
# Fallback to default name
|
||||||
@ -705,6 +721,7 @@ async def test_dhcp_discovery_with_creds(
|
|||||||
|
|
||||||
result = await oauth.async_configure(result, {"project_id": PROJECT_ID})
|
result = await oauth.async_configure(result, {"project_id": PROJECT_ID})
|
||||||
await oauth.async_oauth_web_flow(result)
|
await oauth.async_oauth_web_flow(result)
|
||||||
|
oauth.async_mock_refresh()
|
||||||
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
@ -726,3 +743,36 @@ async def test_dhcp_discovery_with_creds(
|
|||||||
"type": "Bearer",
|
"type": "Bearer",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("status_code", "error_reason"),
|
||||||
|
[
|
||||||
|
(HTTPStatus.UNAUTHORIZED, "oauth_unauthorized"),
|
||||||
|
(HTTPStatus.NOT_FOUND, "oauth_failed"),
|
||||||
|
(HTTPStatus.INTERNAL_SERVER_ERROR, "oauth_failed"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_token_error(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
oauth: OAuthFixture,
|
||||||
|
subscriber: FakeSubscriber,
|
||||||
|
setup_platform: PlatformSetup,
|
||||||
|
status_code: HTTPStatus,
|
||||||
|
error_reason: str,
|
||||||
|
) -> None:
|
||||||
|
"""Check full flow."""
|
||||||
|
await setup_platform()
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
await oauth.async_app_creds_flow(result)
|
||||||
|
oauth.aioclient_mock.post(
|
||||||
|
OAUTH2_TOKEN,
|
||||||
|
status=status_code,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await oauth.async_configure(result, user_input=None)
|
||||||
|
assert result.get("type") == "abort"
|
||||||
|
assert result.get("reason") == error_reason
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
"""Tests for the Somfy config flow."""
|
"""Tests for the Somfy config flow."""
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from http import HTTPStatus
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
from typing import Any
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
@ -339,7 +341,7 @@ async def test_abort_on_oauth_timeout_error(
|
|||||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||||
|
|
||||||
assert result["type"] == data_entry_flow.FlowResultType.ABORT
|
assert result["type"] == data_entry_flow.FlowResultType.ABORT
|
||||||
assert result["reason"] == "oauth2_timeout"
|
assert result["reason"] == "oauth_timeout"
|
||||||
|
|
||||||
|
|
||||||
async def test_step_discovery(hass: HomeAssistant, flow_handler, local_impl) -> None:
|
async def test_step_discovery(hass: HomeAssistant, flow_handler, local_impl) -> None:
|
||||||
@ -387,6 +389,164 @@ async def test_abort_discovered_multiple(
|
|||||||
assert result["reason"] == "already_in_progress"
|
assert result["reason"] == "already_in_progress"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("status_code", "error_body", "error_reason", "error_log"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
HTTPStatus.UNAUTHORIZED,
|
||||||
|
{},
|
||||||
|
"oauth_unauthorized",
|
||||||
|
"Token request failed (unknown): unknown",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
HTTPStatus.NOT_FOUND,
|
||||||
|
{},
|
||||||
|
"oauth_failed",
|
||||||
|
"Token request failed (unknown): unknown",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
{},
|
||||||
|
"oauth_failed",
|
||||||
|
"Token request failed (unknown): unknown",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
HTTPStatus.BAD_REQUEST,
|
||||||
|
{
|
||||||
|
"error": "invalid_request",
|
||||||
|
"error_description": "Request was missing the 'redirect_uri' parameter.",
|
||||||
|
"error_uri": "See the full API docs at https://authorization-server.com/docs/access_token",
|
||||||
|
},
|
||||||
|
"oauth_failed",
|
||||||
|
"Token request failed (invalid_request): Request was missing the",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_abort_if_oauth_token_error(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
flow_handler,
|
||||||
|
local_impl,
|
||||||
|
hass_client_no_auth: ClientSessionGenerator,
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
current_request_with_host: None,
|
||||||
|
status_code: HTTPStatus,
|
||||||
|
error_body: dict[str, Any],
|
||||||
|
error_reason: str,
|
||||||
|
error_log: str,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Check error when obtaining an 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.FlowResultType.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.FlowResultType.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,
|
||||||
|
status=status_code,
|
||||||
|
json=error_body,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.FlowResultType.ABORT
|
||||||
|
assert result["reason"] == error_reason
|
||||||
|
assert error_log in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_abort_if_oauth_token_closing_error(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
flow_handler,
|
||||||
|
local_impl,
|
||||||
|
hass_client_no_auth: ClientSessionGenerator,
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
current_request_with_host: None,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Check error when obtaining an 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.FlowResultType.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.FlowResultType.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,
|
||||||
|
status=HTTPStatus.UNAUTHORIZED,
|
||||||
|
closing=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
with caplog.at_level(logging.DEBUG):
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||||
|
assert "Token request failed (unknown): unknown" in caplog.text
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "oauth_unauthorized"
|
||||||
|
|
||||||
|
|
||||||
async def test_abort_discovered_existing_entries(
|
async def test_abort_discovered_existing_entries(
|
||||||
hass: HomeAssistant, flow_handler, local_impl
|
hass: HomeAssistant, flow_handler, local_impl
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -7,7 +7,11 @@ from unittest import mock
|
|||||||
from urllib.parse import parse_qs
|
from urllib.parse import parse_qs
|
||||||
|
|
||||||
from aiohttp import ClientSession
|
from aiohttp import ClientSession
|
||||||
from aiohttp.client_exceptions import ClientError, ClientResponseError
|
from aiohttp.client_exceptions import (
|
||||||
|
ClientConnectionError,
|
||||||
|
ClientError,
|
||||||
|
ClientResponseError,
|
||||||
|
)
|
||||||
from aiohttp.streams import StreamReader
|
from aiohttp.streams import StreamReader
|
||||||
from multidict import CIMultiDict
|
from multidict import CIMultiDict
|
||||||
from yarl import URL
|
from yarl import URL
|
||||||
@ -53,6 +57,7 @@ class AiohttpClientMocker:
|
|||||||
exc=None,
|
exc=None,
|
||||||
cookies=None,
|
cookies=None,
|
||||||
side_effect=None,
|
side_effect=None,
|
||||||
|
closing=None,
|
||||||
):
|
):
|
||||||
"""Mock a request."""
|
"""Mock a request."""
|
||||||
if not isinstance(url, RETYPE):
|
if not isinstance(url, RETYPE):
|
||||||
@ -72,6 +77,7 @@ class AiohttpClientMocker:
|
|||||||
exc=exc,
|
exc=exc,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
side_effect=side_effect,
|
side_effect=side_effect,
|
||||||
|
closing=closing,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -165,6 +171,7 @@ class AiohttpClientMockResponse:
|
|||||||
exc=None,
|
exc=None,
|
||||||
headers=None,
|
headers=None,
|
||||||
side_effect=None,
|
side_effect=None,
|
||||||
|
closing=None,
|
||||||
):
|
):
|
||||||
"""Initialize a fake response."""
|
"""Initialize a fake response."""
|
||||||
if json is not None:
|
if json is not None:
|
||||||
@ -178,9 +185,10 @@ class AiohttpClientMockResponse:
|
|||||||
self.method = method
|
self.method = method
|
||||||
self._url = url
|
self._url = url
|
||||||
self.status = status
|
self.status = status
|
||||||
self.response = response
|
self._response = response
|
||||||
self.exc = exc
|
self.exc = exc
|
||||||
self.side_effect = side_effect
|
self.side_effect = side_effect
|
||||||
|
self.closing = closing
|
||||||
self._headers = CIMultiDict(headers or {})
|
self._headers = CIMultiDict(headers or {})
|
||||||
self._cookies = {}
|
self._cookies = {}
|
||||||
|
|
||||||
@ -272,6 +280,13 @@ class AiohttpClientMockResponse:
|
|||||||
def close(self):
|
def close(self):
|
||||||
"""Mock close."""
|
"""Mock close."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def response(self):
|
||||||
|
"""Property method to expose the response to other read methods."""
|
||||||
|
if self.closing:
|
||||||
|
raise ClientConnectionError("Connection closed")
|
||||||
|
return self._response
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def mock_aiohttp_client():
|
def mock_aiohttp_client():
|
||||||
|
Loading…
x
Reference in New Issue
Block a user