diff --git a/miniflux.py b/miniflux.py index ac94fc6..92a4f56 100644 --- a/miniflux.py +++ b/miniflux.py @@ -48,10 +48,11 @@ def get_error_reason(self) -> str: Returns: str: The error message from the response body, or a default message if not available. """ - result = self._response.json() default_reason = f"status_code={self.status_code}" - if isinstance(result, dict): - return result.get("error_message", default_reason) + if self._response.headers.get("Content-Type") == "application/json": + result = self._response.json() + if isinstance(result, dict): + return result.get("error_message", default_reason) return default_reason @@ -133,14 +134,10 @@ def __init__( ValueError: If neither `api_key` nor both `username` and `password` are provided. """ if not base_url.startswith(("http://", "https://")): - raise ValueError( - "base_url must be a valid URL starting with http:// or https://" - ) + raise ValueError("base_url must be a valid URL starting with http:// or https://") if not api_key and not (username and password): - raise ValueError( - "Either api_key or both username and password must be provided" - ) + raise ValueError("Either api_key or both username and password must be provided") self._base_url = base_url.rstrip("/") self._timeout = timeout @@ -391,9 +388,7 @@ def get_icon_by_feed_id(self, feed_id: int) -> dict: """ return self.get_feed_icon(feed_id) - def create_feed( - self, feed_url: str, category_id: Optional[int] = None, **kwargs - ) -> int: + def create_feed(self, feed_url: str, category_id: Optional[int] = None, **kwargs) -> int: """ Create a new feed. @@ -659,9 +654,7 @@ def get_entries(self, **kwargs) -> dict: return response.json() self._handle_error_response(response) - def update_entry( - self, entry_id: int, title: Optional[str] = None, content: Optional[str] = None - ) -> dict: + def update_entry(self, entry_id: int, title: Optional[str] = None, content: Optional[str] = None) -> dict: """ Update an entry. @@ -781,9 +774,7 @@ def get_enclosure(self, enclosure_id: int) -> dict: return response.json() self._handle_error_response(response) - def update_enclosure( - self, enclosure_id: int, media_progression: Optional[int] = None - ) -> bool: + def update_enclosure(self, enclosure_id: int, media_progression: Optional[int] = None) -> bool: """ Update an enclosure. diff --git a/tests/test_client.py b/tests/test_client.py index afb2bba..5cc069a 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -34,6 +34,7 @@ ResourceNotFound, ServerError, ) + import requests from requests.exceptions import Timeout @@ -42,6 +43,7 @@ class TestMinifluxClient(unittest.TestCase): def test_get_error_reason(self): response = mock.Mock() response.status_code = 404 + response.headers = {"Content-Type": "application/json"} response.json.return_value = {"error_message": "some error"} error = ResourceNotFound(response) self.assertEqual(error.status_code, 404) @@ -50,6 +52,7 @@ def test_get_error_reason(self): def test_get_error_without_reason(self): response = mock.Mock() response.status_code = 404 + response.headers = {"Content-Type": "application/json"} response.json.return_value = {} error = ResourceNotFound(response) self.assertEqual(error.status_code, 404) @@ -58,16 +61,37 @@ def test_get_error_without_reason(self): def test_get_error_with_bad_response(self): response = mock.Mock() response.status_code = 404 + response.headers = {"Content-Type": "application/json"} response.json.return_value = None error = ResourceNotFound(response) self.assertEqual(error.status_code, 404) self.assertEqual(error.get_error_reason(), "status_code=404") + def test_get_error_reason_without_json_content_type(self): + response = mock.Mock() + response.status_code = 500 + response.headers = {"Content-Type": "text/html"} + response.json = mock.Mock() + + error = ServerError(response) + + self.assertEqual(error.get_error_reason(), "status_code=500") + response.json.assert_not_called() + + def test_get_error_reason_without_content_type_header(self): + response = mock.Mock() + response.status_code = 403 + response.headers = {} + response.json = mock.Mock() + + error = AccessForbidden(response) + + self.assertEqual(error.get_error_reason(), "status_code=403") + response.json.assert_not_called() + def test_base_url_with_trailing_slash(self): session = requests.Session() - expected_result = [ - {"url": "http://example.org/feed", "title": "Example", "type": "RSS"} - ] + expected_result = [{"url": "http://example.org/feed", "title": "Example", "type": "RSS"}] response = mock.Mock() response.status_code = 200 @@ -76,9 +100,7 @@ def test_base_url_with_trailing_slash(self): session.post = mock.Mock() session.post.return_value = response - client = miniflux.Client( - "http://localhost/", "username", "password", session=session - ) + client = miniflux.Client("http://localhost/", "username", "password", session=session) result = client.discover("http://example.org/") session.post.assert_called_once_with( @@ -148,9 +170,7 @@ def test_get_me(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.me() session.get.assert_called_once_with( @@ -169,18 +189,14 @@ def test_get_me_with_server_error(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) with self.assertRaises(ClientError): client.me() def test_discover(self): session = requests.Session() - expected_result = [ - {"url": "http://example.org/feed", "title": "Example", "type": "RSS"} - ] + expected_result = [{"url": "http://example.org/feed", "title": "Example", "type": "RSS"}] response = mock.Mock() response.status_code = 200 @@ -189,9 +205,7 @@ def test_discover(self): session.post = mock.Mock() session.post.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.discover("http://example.org/") session.post.assert_called_once_with( @@ -210,9 +224,7 @@ def test_discover(self): def test_discover_with_credentials(self): session = requests.Session() - expected_result = [ - {"url": "http://example.org/feed", "title": "Example", "type": "RSS"} - ] + expected_result = [{"url": "http://example.org/feed", "title": "Example", "type": "RSS"}] response = mock.Mock() response.status_code = 200 @@ -221,9 +233,7 @@ def test_discover_with_credentials(self): session.post = mock.Mock() session.post.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.discover( "http://example.org/", username="foobar", @@ -257,9 +267,7 @@ def test_discover_with_server_error(self): session.post = mock.Mock() session.post.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) with self.assertRaises(ClientError): client.discover("http://example.org/") @@ -275,9 +283,7 @@ def test_export(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.export() session.get.assert_called_once_with( @@ -297,9 +303,7 @@ def test_import(self): session.post = mock.Mock() session.post.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) client.import_feeds(input_data) session.post.assert_called_once_with( @@ -319,9 +323,7 @@ def test_import_failure(self): session.post = mock.Mock() session.post.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) with self.assertRaises(ClientError): client.import_feeds(input_data) @@ -343,9 +345,7 @@ def test_get_feed(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.get_feed(123) session.get.assert_called_once_with( @@ -370,9 +370,7 @@ def test_get_feed_icon(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.get_icon_by_feed_id(123) session.get.assert_called_once_with( @@ -397,9 +395,7 @@ def test_get_icon(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.get_icon(11) session.get.assert_called_once_with( @@ -420,9 +416,7 @@ def test_create_feed(self): session.post = mock.Mock() session.post.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.create_feed("http://example.org/feed", 123) session.post.assert_called_once_with( @@ -452,9 +446,7 @@ def test_create_feed_with_no_category(self): session.post = mock.Mock() session.post.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.create_feed("http://example.org/feed") session.post.assert_called_once_with( @@ -484,12 +476,8 @@ def test_create_feed_with_credentials(self): session.post = mock.Mock() session.post.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) - result = client.create_feed( - "http://example.org/feed", 123, username="foobar", password="secret" - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) + result = client.create_feed("http://example.org/feed", 123, username="foobar", password="secret") session.post.assert_called_once_with( "http://localhost/v1/feeds", @@ -518,9 +506,7 @@ def test_create_feed_with_crawler_enabled(self): session.post = mock.Mock() session.post.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.create_feed("http://example.org/feed", 123, crawler=True) session.post.assert_called_once_with( @@ -550,12 +536,8 @@ def test_create_feed_with_custom_user_agent_and_crawler_disabled(self): session.post = mock.Mock() session.post.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) - result = client.create_feed( - "http://example.org/feed", 123, crawler=False, user_agent="GoogleBot" - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) + result = client.create_feed("http://example.org/feed", 123, crawler=False, user_agent="GoogleBot") session.post.assert_called_once_with( "http://localhost/v1/feeds", @@ -585,9 +567,7 @@ def test_update_feed(self): session.put = mock.Mock() session.put.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.update_feed(123, crawler=True, username="test") session.put.assert_called_once_with( @@ -616,9 +596,7 @@ def test_refresh_all_feeds(self): session.put = mock.Mock() session.put.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.refresh_all_feeds() session.put.assert_called_once_with( @@ -639,9 +617,7 @@ def test_refresh_feed(self): session.put = mock.Mock() session.put.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.refresh_feed(123) session.put.assert_called_once_with( @@ -662,9 +638,7 @@ def test_refresh_category(self): session.put = mock.Mock() session.put.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.refresh_category(123) session.put.assert_called_once_with( @@ -685,9 +659,7 @@ def test_get_feed_entry(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.get_feed_entry(123, 456) session.get.assert_called_once_with( @@ -708,9 +680,7 @@ def test_get_feed_entries(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.get_feed_entries(123) session.get.assert_called_once_with( @@ -732,9 +702,7 @@ def test_get_feed_entries_with_direction_param(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.get_feed_entries(123, direction="asc") session.get.assert_called_once_with( @@ -756,9 +724,7 @@ def test_import_entry(self): session.post = mock.Mock() session.post.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.import_entry( 123, url="http://example.org/article.html", @@ -794,9 +760,7 @@ def test_import_entry_with_published_at_timestamp(self): session.post = mock.Mock() session.post.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.import_entry( 123, url="http://example.org/article.html", @@ -826,9 +790,7 @@ def test_import_entry_when_existing(self): session.post = mock.Mock() session.post.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.import_entry(123, url="http://example.org/article.html") session.post.assert_called_once_with( @@ -845,9 +807,7 @@ def test_import_entry_when_existing(self): def test_import_entry_without_url(self): session = requests.Session() - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) with self.assertRaises(ValueError): client.import_entry(123, url="") @@ -917,9 +877,7 @@ def test_get_entry(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.get_entry(123) session.get.assert_called_once_with( @@ -940,9 +898,7 @@ def test_fetch_entry_content(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.fetch_entry_content(123) session.get.assert_called_once_with( @@ -963,9 +919,7 @@ def test_get_entries(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.get_entries(status="unread", limit=10, offset=5) session.get.assert_called_once_with( @@ -988,9 +942,7 @@ def test_get_entries_with_before_param(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.get_entries(before=param_value) session.get.assert_called_once_with( @@ -1012,9 +964,7 @@ def test_get_entries_with_starred_param(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.get_entries(starred=True) session.get.assert_called_once_with( @@ -1036,9 +986,7 @@ def test_get_entries_with_starred_param_at_false(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.get_entries(starred=False, after_entry_id=123) session.get.assert_called_once_with( @@ -1060,9 +1008,7 @@ def test_get_user_by_id(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.get_user_by_id(123) session.get.assert_called_once_with( @@ -1082,9 +1028,7 @@ def test_get_inexisting_user(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) with self.assertRaises(ResourceNotFound): client.get_user_by_id(123) @@ -1100,9 +1044,7 @@ def test_get_user_by_username(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.get_user_by_username("foobar") session.get.assert_called_once_with( @@ -1123,9 +1065,7 @@ def test_update_user(self): session.put = mock.Mock() session.put.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.update_user(123, theme="black", language="fr_FR") session.put.assert_called_once_with( @@ -1148,9 +1088,7 @@ def test_timeout(self): session.get = mock.Mock() session.get.side_effect = Timeout() - client = miniflux.Client( - "http://localhost", "username", "password", 1.0, session=session - ) + client = miniflux.Client("http://localhost", "username", "password", 1.0, session=session) with self.assertRaises(Timeout): client.export() @@ -1208,9 +1146,7 @@ def test_get_category_entry(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.get_category_entry(123, 456) session.get.assert_called_once_with( @@ -1231,9 +1167,7 @@ def test_get_category_entries(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.get_category_entries(123) session.get.assert_called_once_with( @@ -1255,9 +1189,7 @@ def test_update_entry_title(self): session.put = mock.Mock() session.put.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.update_entry(entry_id=123, title="New title") session.put.assert_called_once_with( @@ -1283,9 +1215,7 @@ def test_update_entry_content(self): session.put = mock.Mock() session.put.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.update_entry(entry_id=123, content="New content") session.put.assert_called_once_with( @@ -1309,9 +1239,7 @@ def test_update_entries_status(self): session.put = mock.Mock() session.put.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.update_entries(entry_ids=[123, 456], status="read") session.put.assert_called_once_with( @@ -1338,9 +1266,7 @@ def test_get_enclosure(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.get_enclosure(123) session.get.assert_called_once_with( @@ -1359,9 +1285,7 @@ def test_update_enclosure(self): session.put = mock.Mock() session.put.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) self.assertTrue(client.update_enclosure(123, media_progression=42)) session.put.assert_called_once_with( @@ -1381,9 +1305,7 @@ def test_get_integrations_status(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) result = client.get_integrations_status() session.get.assert_called_once_with( @@ -1464,9 +1386,7 @@ def test_not_found_response(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) with self.assertRaises(ResourceNotFound): client.get_version() @@ -1481,9 +1401,7 @@ def test_unauthorized_response(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) with self.assertRaises(AccessUnauthorized): client.get_version() @@ -1498,9 +1416,7 @@ def test_forbidden_response(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) with self.assertRaises(AccessForbidden): client.get_version() @@ -1515,9 +1431,7 @@ def test_bad_request_response(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) with self.assertRaises(BadRequest): client.get_version() @@ -1532,9 +1446,7 @@ def test_server_error_response(self): session.get = mock.Mock() session.get.return_value = response - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) with self.assertRaises(ServerError): client.get_version() @@ -1542,9 +1454,7 @@ def test_server_error_response(self): def test_session_closed(self): session = mock.Mock() - client = miniflux.Client( - "http://localhost", "username", "password", session=session - ) + client = miniflux.Client("http://localhost", "username", "password", session=session) client.close() session.close.assert_called() @@ -1557,9 +1467,7 @@ def test_context_manager_exit_on_error(self): session = mock.Mock() session.get.return_value = response - with miniflux.Client( - "http://localhost", "username", "password", session=session - ) as client: + with miniflux.Client("http://localhost", "username", "password", session=session) as client: with self.assertRaises(ServerError): client.get_version()