From acf8cfa76f8c5ee8daa6c8aa46fb309ba5bc3c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Tue, 2 Apr 2024 15:54:39 +0200 Subject: [PATCH] Add handlers to list and generate download details (#613) --- hass_nabucasa/cloud_api.py | 53 +++++++++++++++ tests/test_cloud_api.py | 130 +++++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+) diff --git a/hass_nabucasa/cloud_api.py b/hass_nabucasa/cloud_api.py index b393ba9b6..6bba6c900 100644 --- a/hass_nabucasa/cloud_api.py +++ b/hass_nabucasa/cloud_api.py @@ -11,6 +11,7 @@ Concatenate, ParamSpec, TypeVar, + cast, ) from aiohttp import ClientResponse @@ -155,6 +156,58 @@ async def async_voice_connection_details(cloud: Cloud[_ClientT]) -> ClientRespon ) +@_check_token +async def async_files_download_details( + cloud: Cloud[_ClientT], + *, + storage_type: str, + filename: str, +) -> dict[str, Any]: + """Get files download details.""" + resp = await cloud.websession.get( + f"https://{cloud.servicehandlers_server}/files/download_details", + headers={"authorization": cloud.id_token, USER_AGENT: cloud.client.client_name}, + json={ + "storage_type": storage_type, + "filename": filename, + }, + ) + + data: dict[str, Any] = await resp.json() + _do_log_response( + resp, + data["message"] if resp.status == 400 and "message" in data else "", + ) + resp.raise_for_status() + return data + + +@_check_token +async def async_files_list( + cloud: Cloud[_ClientT], + *, + storage_type: str, +) -> list[dict[str, Any]]: + """List files for storage type.""" + resp = await cloud.websession.get( + f"https://{cloud.servicehandlers_server}/files/list", + headers={"authorization": cloud.id_token, USER_AGENT: cloud.client.client_name}, + json={ + "storage_type": storage_type, + }, + ) + + data: dict[str, Any] | list[dict[str, Any]] = await resp.json() + _do_log_response( + resp, + data["message"] + if resp.status == 400 and isinstance(data, dict) and "message" in data + else "", + ) + resp.raise_for_status() + return cast(list[dict[str, Any]], data) + + @_check_token async def async_files_upload_details( cloud: Cloud[_ClientT], diff --git a/tests/test_cloud_api.py b/tests/test_cloud_api.py index 210b9440b..971f4519e 100644 --- a/tests/test_cloud_api.py +++ b/tests/test_cloud_api.py @@ -183,6 +183,136 @@ async def test_migrate_paypal_agreement(auth_cloud_mock, aioclient_mock): } +async def test_async_files_download_detils( + auth_cloud_mock: MagicMock, + aioclient_mock: Generator[AiohttpClientMocker, Any, None], + caplog: pytest.LogCaptureFixture, +): + """Test the async_files_download_details function.""" + aioclient_mock.get( + "https://example.com/files/download_details", + json={ + "url": "https://example.com/some/path", + }, + ) + auth_cloud_mock.id_token = "mock-id-token" + auth_cloud_mock.servicehandlers_server = "example.com" + + details = await cloud_api.async_files_download_details( + cloud=auth_cloud_mock, + storage_type="test", + filename="test.txt", + ) + + assert len(aioclient_mock.mock_calls) == 1 + # 2 is the body + assert aioclient_mock.mock_calls[0][2] == { + "filename": "test.txt", + "storage_type": "test", + } + + assert details == { + "url": "https://example.com/some/path", + } + assert "Fetched https://example.com/files/download_details (200)" in caplog.text + + +async def test_async_files_download_details_error( + auth_cloud_mock: MagicMock, + aioclient_mock: Generator[AiohttpClientMocker, Any, None], + caplog: pytest.LogCaptureFixture, +): + """Test the async_files_download_details function with error.""" + aioclient_mock.get( + "https://example.com/files/download_details", + status=400, + json={"message": "Boom!"}, + ) + auth_cloud_mock.id_token = "mock-id-token" + auth_cloud_mock.servicehandlers_server = "example.com" + + with pytest.raises(ClientResponseError): + await cloud_api.async_files_download_details( + cloud=auth_cloud_mock, + storage_type="test", + filename="test.txt", + ) + + assert len(aioclient_mock.mock_calls) == 1 + # 2 is the body + assert aioclient_mock.mock_calls[0][2] == { + "filename": "test.txt", + "storage_type": "test", + } + + assert ( + "Fetched https://example.com/files/download_details (400) Boom!" in caplog.text + ) + + +async def test_async_files_list( + auth_cloud_mock: MagicMock, + aioclient_mock: Generator[AiohttpClientMocker, Any, None], + caplog: pytest.LogCaptureFixture, +): + """Test the async_files_list function.""" + aioclient_mock.get( + "https://example.com/files/list", + json=[{"key": "test.txt", "last_modified": "2021-01-01T00:00:00Z", "size": 2}], + ) + auth_cloud_mock.id_token = "mock-id-token" + auth_cloud_mock.servicehandlers_server = "example.com" + + details = await cloud_api.async_files_list( + cloud=auth_cloud_mock, + storage_type="test", + ) + + assert len(aioclient_mock.mock_calls) == 1 + # 2 is the body + assert aioclient_mock.mock_calls[0][2] == { + "storage_type": "test", + } + + assert details == [ + { + "key": "test.txt", + "last_modified": "2021-01-01T00:00:00Z", + "size": 2, + }, + ] + assert "Fetched https://example.com/files/list (200)" in caplog.text + + +async def test_async_files_list_error( + auth_cloud_mock: MagicMock, + aioclient_mock: Generator[AiohttpClientMocker, Any, None], + caplog: pytest.LogCaptureFixture, +): + """Test the async_files_list function with error listing files.""" + aioclient_mock.get( + "https://example.com/files/list", + status=400, + json={"message": "Boom!"}, + ) + auth_cloud_mock.id_token = "mock-id-token" + auth_cloud_mock.servicehandlers_server = "example.com" + + with pytest.raises(ClientResponseError): + await cloud_api.async_files_list( + cloud=auth_cloud_mock, + storage_type="test", + ) + + assert len(aioclient_mock.mock_calls) == 1 + # 2 is the body + assert aioclient_mock.mock_calls[0][2] == { + "storage_type": "test", + } + + assert "Fetched https://example.com/files/list (400) Boom!" in caplog.text + + async def test_async_files_upload_detils( auth_cloud_mock: MagicMock, aioclient_mock: Generator[AiohttpClientMocker, Any, None],