From bff59c92ed7a53fe49444ebd13a5bf972a4c0bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= <cuu508@gmail.com> Date: Wed, 25 Oct 2023 14:20:51 +0300 Subject: [PATCH] Improve API error handling in hc.front.views.trello_settings --- hc/front/tests/test_trello_settings.py | 21 ++++++++++++----- hc/front/views.py | 25 ++++++++++++++++++--- templates/integrations/trello_settings.html | 5 +++++ 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/hc/front/tests/test_trello_settings.py b/hc/front/tests/test_trello_settings.py index cbff51bd..3fd5a5cf 100644 --- a/hc/front/tests/test_trello_settings.py +++ b/hc/front/tests/test_trello_settings.py @@ -1,10 +1,11 @@ from __future__ import annotations +import json from unittest.mock import Mock, patch from django.test.utils import override_settings -from hc.test import BaseTestCase +from hc.test import BaseTestCase, nolog @override_settings(TRELLO_APP_KEY="foo") @@ -13,9 +14,9 @@ class AddTrelloTestCase(BaseTestCase): @patch("hc.front.views.curl.get", autospec=True) def test_it_works(self, mock_get: Mock) -> None: - mock_get.return_value.json.return_value = [ - {"name": "My Board", "lists": [{"name": "Alerts"}]} - ] + mock_get.return_value.content = json.dumps( + [{"id": "1", "name": "My Board", "lists": [{"id": "2", "name": "Alerts"}]}] + ) self.client.login(username="alice@example.org", password="password") r = self.client.post(self.url) @@ -30,9 +31,19 @@ class AddTrelloTestCase(BaseTestCase): @patch("hc.front.views.curl.get", autospec=True) def test_it_handles_no_lists(self, mock_get: Mock) -> None: - mock_get.return_value.json.return_value = [] + mock_get.return_value.content = "[]" self.client.login(username="alice@example.org", password="password") r = self.client.post(self.url) self.assertNotContains(r, "Please select the Trello list") self.assertContains(r, "Could not find any boards with lists") + + @nolog + @patch("hc.front.views.curl.get", autospec=True) + def test_it_handles_unexpected_response_from_trello(self, mock_get: Mock) -> None: + for sample in ("surprise", "{}", """{"lists": "surprise"}"""): + mock_get.return_value.content = sample + + self.client.login(username="alice@example.org", password="password") + r = self.client.post(self.url) + self.assertContains(r, "Received an unexpected response from Trello") diff --git a/hc/front/views.py b/hc/front/views.py index c2bd2534..a9ffd4d9 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -40,7 +40,7 @@ from django.urls import reverse from django.utils.timezone import now from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST -from pydantic import BaseModel, ValidationError +from pydantic import BaseModel, TypeAdapter, ValidationError from hc.accounts.http import AuthenticatedHttpRequest from hc.accounts.models import Member, Profile, Project @@ -2274,6 +2274,20 @@ def add_apprise(request: AuthenticatedHttpRequest, code: UUID) -> HttpResponse: return render(request, "integrations/add_apprise.html", ctx) +class TrelloList(BaseModel): + id: str + name: str + + +class TrelloBoard(BaseModel): + id: str + name: str + lists: list[TrelloList] + + +TrelloBoards = TypeAdapter(list[TrelloBoard]) + + @require_setting("TRELLO_APP_KEY") @login_required @require_POST @@ -2291,9 +2305,14 @@ def trello_settings(request: AuthenticatedHttpRequest) -> HttpResponse: "list_fields": "id,name", } - boards = curl.get(url, params).json() - num_lists = sum(len(board["lists"]) for board in boards) + result = curl.get(url, params) + try: + boards = TrelloBoards.validate_json(result.content) + except ValidationError: + logger.warning("Unexpected Trello API response: %s", result.text) + return render(request, "integrations/trello_settings.html", {"error": 1}) + num_lists = sum(len(board.lists) for board in boards) ctx = {"token": token, "boards": boards, "num_lists": num_lists} return render(request, "integrations/trello_settings.html", ctx) diff --git a/templates/integrations/trello_settings.html b/templates/integrations/trello_settings.html index 1ea74e53..52eabb16 100644 --- a/templates/integrations/trello_settings.html +++ b/templates/integrations/trello_settings.html @@ -39,6 +39,11 @@ </div> </div> </form> +{% elif error %} +<p class="alert alert-warning"> + Could not retrieve data from your Trello account. + Received an unexpected response from Trello. +</p> {% else %} <p class="alert alert-warning"> Could not find any boards with lists in your Trello account.