From 3e48b220957cb0608e9338b7e400aac215f67866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20I=C3=9Fbr=C3=BCcker?= Date: Fri, 14 May 2021 23:34:53 +0200 Subject: [PATCH] Add settings view tests --- bookmarks/services/importer.py | 6 +- bookmarks/services/parser.py | 6 +- .../tests/resources/invalid_import_file.png | Bin 0 -> 12623 bytes .../resources/simple_valid_import_file.html | 20 +++++ ...import_file_with_one_invalid_bookmark.html | 20 +++++ bookmarks/tests/test_settings_api_view.py | 40 +++++++++ bookmarks/tests/test_settings_export_view.py | 45 ++++++++++ bookmarks/tests/test_settings_general_view.py | 36 ++++++++ bookmarks/tests/test_settings_import_view.py | 77 ++++++++++++++++++ .../tests/test_settings_integrations_view.py | 22 +++++ bookmarks/views/settings.py | 10 +-- 11 files changed, 272 insertions(+), 10 deletions(-) create mode 100644 bookmarks/tests/resources/invalid_import_file.png create mode 100644 bookmarks/tests/resources/simple_valid_import_file.html create mode 100644 bookmarks/tests/resources/simple_valid_import_file_with_one_invalid_bookmark.html create mode 100644 bookmarks/tests/test_settings_api_view.py create mode 100644 bookmarks/tests/test_settings_export_view.py create mode 100644 bookmarks/tests/test_settings_general_view.py create mode 100644 bookmarks/tests/test_settings_import_view.py create mode 100644 bookmarks/tests/test_settings_integrations_view.py diff --git a/bookmarks/services/importer.py b/bookmarks/services/importer.py index 6dfe949..a315019 100644 --- a/bookmarks/services/importer.py +++ b/bookmarks/services/importer.py @@ -3,6 +3,7 @@ from dataclasses import dataclass from datetime import datetime from django.contrib.auth.models import User +from django.utils import timezone from bookmarks.models import Bookmark, parse_tag_string from bookmarks.services.parser import parse, NetscapeBookmark @@ -45,7 +46,10 @@ def _import_bookmark_tag(netscape_bookmark: NetscapeBookmark, user: User): bookmark = _get_or_create_bookmark(netscape_bookmark.href, user) bookmark.url = netscape_bookmark.href - bookmark.date_added = datetime.utcfromtimestamp(int(netscape_bookmark.date_added)).astimezone() + if netscape_bookmark.date_added: + bookmark.date_added = datetime.utcfromtimestamp(int(netscape_bookmark.date_added)).astimezone() + else: + bookmark.date_added = timezone.now() bookmark.date_modified = bookmark.date_added bookmark.unread = False bookmark.title = netscape_bookmark.title diff --git a/bookmarks/services/parser.py b/bookmarks/services/parser.py index 39079a4..ed134da 100644 --- a/bookmarks/services/parser.py +++ b/bookmarks/services/parser.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -from datetime import datetime import pyparsing as pp @@ -9,7 +8,7 @@ class NetscapeBookmark: href: str title: str description: str - date_added: int + date_added: str tag_string: str @@ -17,8 +16,7 @@ def extract_bookmark_link(tag): href = tag[0].href title = tag[0].text tag_string = tag[0].tags - date_added_string = tag[0].add_date if tag[0].add_date else datetime.now().timestamp() - date_added = int(date_added_string) + date_added = tag[0].add_date return { 'href': href, diff --git a/bookmarks/tests/resources/invalid_import_file.png b/bookmarks/tests/resources/invalid_import_file.png new file mode 100644 index 0000000000000000000000000000000000000000..f5c32c2a96ba52e230da284d0678f3b09143d53a GIT binary patch literal 12623 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 literal 0 HcmV?d00001 diff --git a/bookmarks/tests/resources/simple_valid_import_file.html b/bookmarks/tests/resources/simple_valid_import_file.html new file mode 100644 index 0000000..74435aa --- /dev/null +++ b/bookmarks/tests/resources/simple_valid_import_file.html @@ -0,0 +1,20 @@ + + + + +Bookmarks + +

Bookmarks

+ +

+ +

test title 1 +
test description 1 + +
test title 2 +
test description 2 + +
test title 3 +
test description 3 + +

\ No newline at end of file diff --git a/bookmarks/tests/resources/simple_valid_import_file_with_one_invalid_bookmark.html b/bookmarks/tests/resources/simple_valid_import_file_with_one_invalid_bookmark.html new file mode 100644 index 0000000..f3eec2f --- /dev/null +++ b/bookmarks/tests/resources/simple_valid_import_file_with_one_invalid_bookmark.html @@ -0,0 +1,20 @@ + + + + +Bookmarks + +

Bookmarks

+ +

+ +

test title 1 +
test description 1 + +
test title 2 +
test description 2 + +
test title 3 +
test description 3 + +

\ No newline at end of file diff --git a/bookmarks/tests/test_settings_api_view.py b/bookmarks/tests/test_settings_api_view.py new file mode 100644 index 0000000..587728b --- /dev/null +++ b/bookmarks/tests/test_settings_api_view.py @@ -0,0 +1,40 @@ +from django.test import TestCase +from django.urls import reverse +from rest_framework.authtoken.models import Token + +from bookmarks.tests.helpers import BookmarkFactoryMixin + + +class SettingsApiViewTestCase(TestCase, BookmarkFactoryMixin): + + def setUp(self) -> None: + user = self.get_or_create_test_user() + self.client.force_login(user) + + def test_should_render_successfully(self): + response = self.client.get(reverse('bookmarks:settings.api')) + + self.assertEqual(response.status_code, 200) + + def test_should_check_authentication(self): + self.client.logout() + response = self.client.get(reverse('bookmarks:settings.api'), follow=True) + + self.assertRedirects(response, reverse('login') + '?next=' + reverse('bookmarks:settings.api')) + + def test_should_generate_api_token_if_not_exists(self): + self.assertEqual(Token.objects.count(), 0) + + self.client.get(reverse('bookmarks:settings.api')) + + self.assertEqual(Token.objects.count(), 1) + token = Token.objects.first() + self.assertEqual(token.user, self.user) + + def test_should_not_generate_api_token_if_exists(self): + Token.objects.get_or_create(user=self.user) + self.assertEqual(Token.objects.count(), 1) + + self.client.get(reverse('bookmarks:settings.api')) + + self.assertEqual(Token.objects.count(), 1) diff --git a/bookmarks/tests/test_settings_export_view.py b/bookmarks/tests/test_settings_export_view.py new file mode 100644 index 0000000..e1d56b1 --- /dev/null +++ b/bookmarks/tests/test_settings_export_view.py @@ -0,0 +1,45 @@ +from unittest.mock import patch + +from django.test import TestCase +from django.urls import reverse + +from bookmarks.tests.helpers import BookmarkFactoryMixin + + +class SettingsExportViewTestCase(TestCase, BookmarkFactoryMixin): + + def setUp(self) -> None: + user = self.get_or_create_test_user() + self.client.force_login(user) + + def assertFormErrorHint(self, response, text: str): + self.assertContains(response, '

') + self.assertContains(response, text) + + def test_should_export_successfully(self): + self.setup_bookmark(tags=[self.setup_tag()]) + self.setup_bookmark(tags=[self.setup_tag()]) + self.setup_bookmark(tags=[self.setup_tag()]) + + response = self.client.get( + reverse('bookmarks:settings.export'), + follow=True + ) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response['content-type'], 'text/plain; charset=UTF-8') + self.assertEqual(response['Content-Disposition'], 'attachment; filename="bookmarks.html"') + + def test_should_check_authentication(self): + self.client.logout() + response = self.client.get(reverse('bookmarks:settings.export'), follow=True) + + self.assertRedirects(response, reverse('login') + '?next=' + reverse('bookmarks:settings.export')) + + def test_should_show_hint_when_export_raises_error(self): + with patch('bookmarks.services.exporter.export_netscape_html') as mock_export_netscape_html: + mock_export_netscape_html.side_effect = Exception('Nope') + response = self.client.get(reverse('bookmarks:settings.export'), follow=True) + + self.assertTemplateUsed(response, 'settings/general.html') + self.assertFormErrorHint(response, 'An error occurred during bookmark export.') diff --git a/bookmarks/tests/test_settings_general_view.py b/bookmarks/tests/test_settings_general_view.py new file mode 100644 index 0000000..642c0c0 --- /dev/null +++ b/bookmarks/tests/test_settings_general_view.py @@ -0,0 +1,36 @@ +from django.test import TestCase +from django.urls import reverse + +from bookmarks.tests.helpers import BookmarkFactoryMixin +from bookmarks.models import UserProfile + + +class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin): + + def setUp(self) -> None: + user = self.get_or_create_test_user() + self.client.force_login(user) + + def test_should_render_successfully(self): + response = self.client.get(reverse('bookmarks:settings.general')) + + self.assertEqual(response.status_code, 200) + + def test_should_check_authentication(self): + self.client.logout() + response = self.client.get(reverse('bookmarks:settings.general'), follow=True) + + self.assertRedirects(response, reverse('login') + '?next=' + reverse('bookmarks:settings.general')) + + def test_should_save_profile(self): + form_data = { + 'theme': UserProfile.THEME_DARK, + 'bookmark_date_display': UserProfile.BOOKMARK_DATE_DISPLAY_HIDDEN, + } + response = self.client.post(reverse('bookmarks:settings.general'), form_data) + + self.user.profile.refresh_from_db() + + self.assertEqual(response.status_code, 200) + self.assertEqual(self.user.profile.theme, form_data['theme']) + self.assertEqual(self.user.profile.bookmark_date_display, form_data['bookmark_date_display']) diff --git a/bookmarks/tests/test_settings_import_view.py b/bookmarks/tests/test_settings_import_view.py new file mode 100644 index 0000000..b5c609a --- /dev/null +++ b/bookmarks/tests/test_settings_import_view.py @@ -0,0 +1,77 @@ +from django.test import TestCase +from django.urls import reverse + +from bookmarks.tests.helpers import BookmarkFactoryMixin + + +class SettingsImportViewTestCase(TestCase, BookmarkFactoryMixin): + + def setUp(self) -> None: + user = self.get_or_create_test_user() + self.client.force_login(user) + + def assertFormSuccessHint(self, response, text: str): + self.assertContains(response, '
') + self.assertContains(response, text) + + def assertNoFormSuccessHint(self, response): + self.assertNotContains(response, '
') + + def assertFormErrorHint(self, response, text: str): + self.assertContains(response, '
') + self.assertContains(response, text) + + def assertNoFormErrorHint(self, response): + self.assertNotContains(response, '
') + + def test_should_import_successfully(self): + with open('bookmarks/tests/resources/simple_valid_import_file.html') as import_file: + response = self.client.post( + reverse('bookmarks:settings.import'), + {'import_file': import_file}, + follow=True + ) + + self.assertRedirects(response, reverse('bookmarks:settings.general')) + self.assertFormSuccessHint(response, '3 bookmarks were successfully imported') + self.assertNoFormErrorHint(response) + + def test_should_check_authentication(self): + self.client.logout() + response = self.client.get(reverse('bookmarks:settings.import'), follow=True) + + self.assertRedirects(response, reverse('login') + '?next=' + reverse('bookmarks:settings.import')) + + def test_should_show_hint_if_there_is_no_file(self): + response = self.client.post( + reverse('bookmarks:settings.import'), + follow=True + ) + + self.assertRedirects(response, reverse('bookmarks:settings.general')) + self.assertNoFormSuccessHint(response) + self.assertFormErrorHint(response, 'Please select a file to import.') + + def test_should_show_hint_if_import_raises_exception(self): + with open('bookmarks/tests/resources/invalid_import_file.png', 'rb') as import_file: + response = self.client.post( + reverse('bookmarks:settings.import'), + {'import_file': import_file}, + follow=True + ) + + self.assertRedirects(response, reverse('bookmarks:settings.general')) + self.assertNoFormSuccessHint(response) + self.assertFormErrorHint(response, 'An error occurred during bookmark import.') + + def test_should_show_respective_hints_if_not_all_bookmarks_were_imported_successfully(self): + with open('bookmarks/tests/resources/simple_valid_import_file_with_one_invalid_bookmark.html') as import_file: + response = self.client.post( + reverse('bookmarks:settings.import'), + {'import_file': import_file}, + follow=True + ) + + self.assertRedirects(response, reverse('bookmarks:settings.general')) + self.assertFormSuccessHint(response, '2 bookmarks were successfully imported') + self.assertFormErrorHint(response, '1 bookmarks could not be imported') diff --git a/bookmarks/tests/test_settings_integrations_view.py b/bookmarks/tests/test_settings_integrations_view.py new file mode 100644 index 0000000..914b79f --- /dev/null +++ b/bookmarks/tests/test_settings_integrations_view.py @@ -0,0 +1,22 @@ +from django.test import TestCase +from django.urls import reverse + +from bookmarks.tests.helpers import BookmarkFactoryMixin + + +class SettingsIntegrationsViewTestCase(TestCase, BookmarkFactoryMixin): + + def setUp(self) -> None: + user = self.get_or_create_test_user() + self.client.force_login(user) + + def test_should_render_successfully(self): + response = self.client.get(reverse('bookmarks:settings.integrations')) + + self.assertEqual(response.status_code, 200) + + def test_should_check_authentication(self): + self.client.logout() + response = self.client.get(reverse('bookmarks:settings.integrations'), follow=True) + + self.assertRedirects(response, reverse('login') + '?next=' + reverse('bookmarks:settings.integrations')) diff --git a/bookmarks/views/settings.py b/bookmarks/views/settings.py index d3d9b17..da97e23 100644 --- a/bookmarks/views/settings.py +++ b/bookmarks/views/settings.py @@ -9,8 +9,8 @@ from rest_framework.authtoken.models import Token from bookmarks.models import UserProfileForm from bookmarks.queries import query_bookmarks -from bookmarks.services.exporter import export_netscape_html -from bookmarks.services.importer import import_netscape_html +from bookmarks.services import exporter +from bookmarks.services import importer logger = logging.getLogger(__name__) @@ -55,11 +55,11 @@ def bookmark_import(request): if import_file is None: messages.error(request, 'Please select a file to import.', 'bookmark_import_errors') - return HttpResponseRedirect(reverse('bookmarks:settings.index')) + return HttpResponseRedirect(reverse('bookmarks:settings.general')) try: content = import_file.read().decode() - result = import_netscape_html(content, request.user) + result = importer.import_netscape_html(content, request.user) success_msg = str(result.success) + ' bookmarks were successfully imported.' messages.success(request, success_msg, 'bookmark_import_success') if result.failed > 0: @@ -78,7 +78,7 @@ def bookmark_export(request): # noinspection PyBroadException try: bookmarks = query_bookmarks(request.user, '') - file_content = export_netscape_html(bookmarks) + file_content = exporter.export_netscape_html(bookmarks) response = HttpResponse(content_type='text/plain; charset=UTF-8') response['Content-Disposition'] = 'attachment; filename="bookmarks.html"'