From 3c22eca5dca11c14bbe8573529dba8440a13f51f Mon Sep 17 00:00:00 2001 From: Jakub Fidler <31575114+RisingOrange@users.noreply.github.com> Date: Mon, 25 Mar 2024 18:01:25 +0100 Subject: [PATCH] [BUILD-254] fix: Handle deck updates response with empty notes correctly (#927) * fix: Handle deck updates response with empty notes correctly * Add test * Fix exception handling handling for client tests --- ankihub/ankihub_client/ankihub_client.py | 3 ++ ...ckUpdates.test_get_empty_deck_updates.yaml | 54 +++++++++++++++++++ tests/client/test_client.py | 51 +++++++++++++----- 3 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 tests/client/cassettes/TestGetDeckUpdates.test_get_empty_deck_updates.yaml diff --git a/ankihub/ankihub_client/ankihub_client.py b/ankihub/ankihub_client/ankihub_client.py index b8ea979b2..92035e146 100644 --- a/ankihub/ankihub_client/ankihub_client.py +++ b/ankihub/ankihub_client/ankihub_client.py @@ -717,6 +717,9 @@ def get_deck_updates( if should_cancel and should_cancel(): return None + if not chunk.notes: + continue + if chunk.from_csv: # The CSV contains all notes, so we assign instead of extending notes_data_from_csv = chunk.notes diff --git a/tests/client/cassettes/TestGetDeckUpdates.test_get_empty_deck_updates.yaml b/tests/client/cassettes/TestGetDeckUpdates.test_get_empty_deck_updates.yaml new file mode 100644 index 000000000..83497b38b --- /dev/null +++ b/tests/client/cassettes/TestGetDeckUpdates.test_get_empty_deck_updates.yaml @@ -0,0 +1,54 @@ +interactions: +- request: + body: '{"username": "test1", "password": "asdf"}' + headers: + Accept: + - application/json; version=18.0 + Content-Length: + - '41' + Content-Type: + - application/json + method: POST + uri: http://localhost:8000/api/login/ + response: + body: + string: '{"expiry":"2024-04-20T11:14:04.085095Z","token":"dcb750c2d67a26450bea083c7ca0e7cbb70f0aece0eda87cfd0993583e51d3bd"}' + headers: + Allow: + - POST, OPTIONS + Content-Language: + - en + Content-Length: + - '115' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Sat, 23 Mar 2024 11:14:04 GMT + Referrer-Policy: + - same-origin + Server: + - WSGIServer/0.2 CPython/3.8.17 + Server-Timing: + - TimerPanel_utime;dur=299.5340000000226;desc="User CPU time", TimerPanel_stime;dur=2.427000000000845;desc="System + CPU time", TimerPanel_total;dur=301.96100000002343;desc="Total CPU time", + TimerPanel_total_time;dur=219.2665290003788;desc="Elapsed time", SQLPanel_sql_time;dur=8.263236000857432;desc="SQL + 16 queries", CachePanel_total_time;dur=0;desc="Cache 0 Calls" + Set-Cookie: + - csrftoken=GEJ14GLNrCpTovniHl5vOIipFded4604; expires=Sat, 22 Mar 2025 11:14:04 + GMT; HttpOnly; Max-Age=31449600; Path=/; SameSite=Lax + - sessionid=zxmvjngepkvsttkrziapyu09k2nl0ffi; expires=Sat, 30 Mar 2024 11:14:04 + GMT; HttpOnly; Max-Age=604800; Path=/; SameSite=Lax + Vary: + - Accept, Cookie, Accept-Language, origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + djdt-store-id: + - ff681edc37564104bbfe37c99a4d6410 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/client/test_client.py b/tests/client/test_client.py index ca9859eeb..904c975bf 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -10,7 +10,7 @@ from copy import deepcopy from datetime import datetime, timedelta, timezone from pathlib import Path -from typing import Callable, Generator, List, cast +from typing import Callable, Generator, List, Optional, cast from unittest.mock import Mock import pytest @@ -253,7 +253,7 @@ def remove_db_dump() -> Generator: elif result.returncode == 1 and "No such container" in result.stderr: # Nothing to do pass - elif "Container" in result.stderr and "is not running" in result.stderr: + elif "container" in result.stderr.lower() and "is not running" in result.stderr: # Container is not running, nothing to do pass elif "docker: command not found" in result.stderr: @@ -1008,21 +1008,44 @@ def test_get_deck_updates_with_external_notes_url( assert deck_updates.notes == [note1_from_json, note2_from_csv] assert deck_updates.latest_update == latest_update + @pytest.mark.vcr() + def test_get_empty_deck_updates( + self, authorized_client_for_user_test1: AnkiHubClient, mocker: MockerFixture + ): + client = authorized_client_for_user_test1 + + # Mock responses from deck updates endpoint + response = self._deck_updates_response_mock_with_json_notes( + notes=[], + latest_update=None, + ) + + mocker.patch( + "ankihub.ankihub_client.ankihub_client.AnkiHubClient._send_request", + return_value=response, + ) + + # Assert that the deck updates are as expected. + deck_updates = client.get_deck_updates(ID_OF_DECK_OF_USER_TEST1, since=None) + assert deck_updates.notes == [] + assert deck_updates.latest_update is None + def _deck_updates_response_mock_with_json_notes( - self, notes: List[NoteInfo], latest_update: datetime + self, notes: List[NoteInfo], latest_update: Optional[datetime] ) -> Mock: result = Mock() note_dicts = [note.to_dict() for note in notes] notes_encoded = gzip.compress(json.dumps(note_dicts).encode("utf-8")) notes_encoded = base64.b85encode(notes_encoded) - latest_update_str = datetime.strftime( - latest_update, ANKIHUB_DATETIME_FORMAT_STR - ) result.json = lambda: { "external_notes_url": None, "next": None, "notes": notes_encoded, - "latest_update": latest_update_str, + "latest_update": datetime.strftime( + latest_update, ANKIHUB_DATETIME_FORMAT_STR + ) + if latest_update + else None, "protected_fields": {}, "protected_tags": [], } @@ -1030,17 +1053,21 @@ def _deck_updates_response_mock_with_json_notes( return result def _deck_updates_response_mock_with_csv_notes( - self, notes: List[NoteInfo], latest_update: datetime, mocker: MockerFixture + self, + notes: List[NoteInfo], + latest_update: Optional[datetime], + mocker: MockerFixture, ) -> Mock: result = Mock() - latest_update_str = datetime.strftime( - latest_update, ANKIHUB_DATETIME_FORMAT_STR - ) result.json = lambda: { "external_notes_url": "test_url", "next": None, "notes": None, - "latest_update": latest_update_str, + "latest_update": datetime.strftime( + latest_update, ANKIHUB_DATETIME_FORMAT_STR + ) + if latest_update + else None, "protected_fields": {}, "protected_tags": [], }