from typing import List from unittest.mock import patch from django.test import TestCase, override_settings from django.utils import timezone from bookmarks.models import Bookmark, Tag, parse_tag_string from bookmarks.services import tasks from bookmarks.services.importer import import_netscape_html, ImportOptions from bookmarks.tests.helpers import ( BookmarkFactoryMixin, ImportTestMixin, BookmarkHtmlTag, disable_logging, ) from bookmarks.utils import parse_timestamp class ImporterTestCase(TestCase, BookmarkFactoryMixin, ImportTestMixin): def assertBookmarksImported(self, html_tags: List[BookmarkHtmlTag]): for html_tag in html_tags: bookmark = Bookmark.objects.get(url=html_tag.href) self.assertIsNotNone(bookmark) self.assertEqual(bookmark.title, html_tag.title) self.assertEqual(bookmark.description, html_tag.description) self.assertEqual(bookmark.date_added, parse_timestamp(html_tag.add_date)) self.assertEqual(bookmark.unread, html_tag.to_read) self.assertEqual(bookmark.shared, not html_tag.private) tag_names = parse_tag_string(html_tag.tags) # Check assigned tags for tag_name in tag_names: tag = next( (tag for tag in bookmark.tags.all() if tag.name == tag_name), None ) self.assertIsNotNone(tag) def test_import(self): html_tags = [ BookmarkHtmlTag( href="https://example.com", title="Example title", description="Example description", add_date="1", tags="example-tag", ), BookmarkHtmlTag( href="https://example.com/foo", title="Foo title", description="", add_date="2", tags="", ), BookmarkHtmlTag( href="https://example.com/bar", title="Bar title", description="Bar description", add_date="3", tags="bar-tag, other-tag", ), BookmarkHtmlTag( href="https://example.com/baz", title="Baz title", description="Baz description", add_date="4", to_read=True, ), ] import_html = self.render_html(tags=html_tags) result = import_netscape_html(import_html, self.get_or_create_test_user()) # Check result self.assertEqual(result.total, 4) self.assertEqual(result.success, 4) self.assertEqual(result.failed, 0) # Check bookmarks bookmarks = Bookmark.objects.all() self.assertEqual(len(bookmarks), 4) self.assertBookmarksImported(html_tags) def test_synchronize(self): # Initial import html_tags = [ BookmarkHtmlTag( href="https://example.com", title="Example title", description="Example description", add_date="1", tags="example-tag", ), BookmarkHtmlTag( href="https://example.com/foo", title="Foo title", description="", add_date="2", tags="", ), BookmarkHtmlTag( href="https://example.com/bar", title="Bar title", description="Bar description", add_date="3", tags="bar-tag, other-tag", ), BookmarkHtmlTag( href="https://example.com/unread", title="Unread title", description="Unread description", add_date="3", to_read=True, ), BookmarkHtmlTag( href="https://example.com/private", title="Private title", description="Private description", add_date="4", private=True, ), ] import_html = self.render_html(tags=html_tags) import_netscape_html(import_html, self.get_or_create_test_user()) # Check bookmarks bookmarks = Bookmark.objects.all() self.assertEqual(len(bookmarks), 5) self.assertBookmarksImported(html_tags) # Change data, add some new data html_tags = [ BookmarkHtmlTag( href="https://example.com", title="Updated Example title", description="Updated Example description", add_date="111", tags="updated-example-tag", ), BookmarkHtmlTag( href="https://example.com/foo", title="Updated Foo title", description="Updated Foo description", add_date="222", tags="new-tag", ), BookmarkHtmlTag( href="https://example.com/bar", title="Updated Bar title", description="Updated Bar description", add_date="333", tags="updated-bar-tag, updated-other-tag", ), BookmarkHtmlTag( href="https://example.com/unread", title="Unread title", description="Unread description", add_date="3", to_read=False, ), BookmarkHtmlTag( href="https://example.com/private", title="Private title", description="Private description", add_date="4", private=False, ), BookmarkHtmlTag(href="https://baz.com", add_date="444", tags="baz-tag"), ] # Import updated data import_html = self.render_html(tags=html_tags) result = import_netscape_html( import_html, self.get_or_create_test_user(), ImportOptions(map_private_flag=True), ) # Check result self.assertEqual(result.total, 6) self.assertEqual(result.success, 6) self.assertEqual(result.failed, 0) # Check bookmarks bookmarks = Bookmark.objects.all() self.assertEqual(len(bookmarks), 6) self.assertBookmarksImported(html_tags) def test_import_with_some_invalid_bookmarks(self): html_tags = [ BookmarkHtmlTag(href="https://example.com"), # Invalid URL BookmarkHtmlTag(href="foo.com"), # No URL BookmarkHtmlTag(), ] import_html = self.render_html(tags=html_tags) result = import_netscape_html(import_html, self.get_or_create_test_user()) # Check result self.assertEqual(result.total, 3) self.assertEqual(result.success, 1) self.assertEqual(result.failed, 2) # Check bookmarks bookmarks = Bookmark.objects.all() self.assertEqual(len(bookmarks), 1) self.assertBookmarksImported(html_tags[1:1]) def test_import_invalid_bookmark_does_not_associate_tags(self): html_tags = [ # No URL BookmarkHtmlTag(tags="tag1, tag2, tag3"), ] import_html = self.render_html(tags=html_tags) # Sqlite silently ignores relationships that have a non-persisted bookmark, # thus testing if the bulk create receives no relationships BookmarkToTagRelationShip = Bookmark.tags.through with patch.object( BookmarkToTagRelationShip.objects, "bulk_create" ) as mock_bulk_create: import_netscape_html(import_html, self.get_or_create_test_user()) mock_bulk_create.assert_called_once_with([], ignore_conflicts=True) def test_import_tags(self): html_tags = [ BookmarkHtmlTag(href="https://example.com", tags="tag1"), BookmarkHtmlTag(href="https://foo.com", tags="tag2"), BookmarkHtmlTag(href="https://bar.com", tags="tag3"), ] import_html = self.render_html(tags=html_tags) import_netscape_html(import_html, self.get_or_create_test_user()) self.assertEqual(Tag.objects.count(), 3) def test_create_missing_tags(self): html_tags = [ BookmarkHtmlTag(href="https://example.com", tags="tag1"), BookmarkHtmlTag(href="https://foo.com", tags="tag2"), BookmarkHtmlTag(href="https://bar.com", tags="tag3"), ] import_html = self.render_html(tags=html_tags) import_netscape_html(import_html, self.get_or_create_test_user()) html_tags.append(BookmarkHtmlTag(href="https://baz.com", tags="tag4")) import_html = self.render_html(tags=html_tags) import_netscape_html(import_html, self.get_or_create_test_user()) self.assertEqual(Tag.objects.count(), 4) def test_create_missing_tags_does_not_duplicate_tags(self): html_tags = [ BookmarkHtmlTag(href="https://example.com", tags="tag1"), BookmarkHtmlTag(href="https://foo.com", tags="tag1"), BookmarkHtmlTag(href="https://bar.com", tags="tag1"), ] import_html = self.render_html(tags=html_tags) import_netscape_html(import_html, self.get_or_create_test_user()) self.assertEqual(Tag.objects.count(), 1) def test_should_append_tags_to_bookmark_when_reimporting_with_different_tags(self): html_tags = [ BookmarkHtmlTag(href="https://example.com", tags="tag1"), ] import_html = self.render_html(tags=html_tags) import_netscape_html(import_html, self.get_or_create_test_user()) html_tags.append(BookmarkHtmlTag(href="https://example.com", tags="tag2, tag3")) import_html = self.render_html(tags=html_tags) import_netscape_html(import_html, self.get_or_create_test_user()) self.assertEqual(Bookmark.objects.count(), 1) self.assertEqual(Bookmark.objects.all()[0].tags.all().count(), 3) @override_settings(USE_TZ=False) def test_use_current_date_when_no_add_date(self): test_html = self.render_html( tags_html=f"""
Example.com
Example.com """ ) with patch.object(timezone, "now", return_value=timezone.datetime(2021, 1, 1)): import_netscape_html(test_html, self.get_or_create_test_user()) self.assertEqual(Bookmark.objects.count(), 1) self.assertEqual( Bookmark.objects.all()[0].date_added, timezone.datetime(2021, 1, 1) ) def test_keep_title_if_imported_bookmark_has_empty_title(self): test_html = self.render_html( tags=[BookmarkHtmlTag(href="https://example.com", title="Example.com")] ) import_netscape_html(test_html, self.get_or_create_test_user()) test_html = self.render_html(tags=[BookmarkHtmlTag(href="https://example.com")]) import_netscape_html(test_html, self.get_or_create_test_user()) self.assertEqual(Bookmark.objects.count(), 1) self.assertEqual(Bookmark.objects.all()[0].title, "Example.com") def test_keep_description_if_imported_bookmark_has_empty_description(self): test_html = self.render_html( tags=[ BookmarkHtmlTag(href="https://example.com", description="Example.com") ] ) import_netscape_html(test_html, self.get_or_create_test_user()) test_html = self.render_html(tags=[BookmarkHtmlTag(href="https://example.com")]) import_netscape_html(test_html, self.get_or_create_test_user()) self.assertEqual(Bookmark.objects.count(), 1) self.assertEqual(Bookmark.objects.all()[0].description, "Example.com") def test_replace_whitespace_in_tag_names(self): test_html = self.render_html( tags_html=f"""
Example.com
Example.com """ ) import_netscape_html(test_html, self.get_or_create_test_user()) tags = Tag.objects.all() tag_names = [tag.name for tag in tags] self.assertListEqual(tag_names, ["tag-1", "tag-2", "tag-3"]) @disable_logging def test_validate_empty_or_missing_bookmark_url(self): test_html = self.render_html( tags_html=f"""
Empty URL
Empty URL
Missing URL
Missing URL """ ) import_result = import_netscape_html(test_html, self.get_or_create_test_user()) self.assertEqual(Bookmark.objects.count(), 0) self.assertEqual(import_result.success, 0) self.assertEqual(import_result.failed, 2) def test_private_flag(self): # does not map private flag if not enabled in options test_html = self.render_html( tags_html="""
Example title 1
Example description 1
Example title 2
Example description 2
Example title 3
Example description 3
""" ) import_netscape_html(test_html, self.get_or_create_test_user(), ImportOptions()) self.assertEqual(Bookmark.objects.count(), 3) self.assertEqual(Bookmark.objects.all()[0].shared, False) self.assertEqual(Bookmark.objects.all()[1].shared, False) self.assertEqual(Bookmark.objects.all()[2].shared, False) # does map private flag if enabled in options Bookmark.objects.all().delete() import_netscape_html( test_html, self.get_or_create_test_user(), ImportOptions(map_private_flag=True), ) bookmark1 = Bookmark.objects.get(url="https://example.com/1") bookmark2 = Bookmark.objects.get(url="https://example.com/2") bookmark3 = Bookmark.objects.get(url="https://example.com/3") self.assertEqual(bookmark1.shared, False) self.assertEqual(bookmark2.shared, False) self.assertEqual(bookmark3.shared, True) def test_archived_state(self): test_html = self.render_html( tags_html="""
Example title 1
Example description 1
Example title 2
Example description 2
Example title 3
Example description 3
""" ) import_netscape_html(test_html, self.get_or_create_test_user(), ImportOptions()) self.assertEqual(Bookmark.objects.count(), 3) self.assertEqual(Bookmark.objects.all()[0].is_archived, True) self.assertEqual(Bookmark.objects.all()[1].is_archived, False) self.assertEqual(Bookmark.objects.all()[2].is_archived, False) tags = Tag.objects.all() self.assertEqual(len(tags), 2) self.assertEqual(tags[0].name, "tag1") self.assertEqual(tags[1].name, "tag2") def test_notes(self): # initial notes test_html = self.render_html( tags_html="""
Example title
Example description[linkding-notes]Example notes[/linkding-notes] """ ) import_netscape_html(test_html, self.get_or_create_test_user(), ImportOptions()) self.assertEqual(Bookmark.objects.count(), 1) self.assertEqual(Bookmark.objects.all()[0].description, "Example description") self.assertEqual(Bookmark.objects.all()[0].notes, "Example notes") # update notes test_html = self.render_html( tags_html="""
Example title
Example description[linkding-notes]Updated notes[/linkding-notes] """ ) import_netscape_html(test_html, self.get_or_create_test_user(), ImportOptions()) self.assertEqual(Bookmark.objects.count(), 1) self.assertEqual(Bookmark.objects.all()[0].description, "Example description") self.assertEqual(Bookmark.objects.all()[0].notes, "Updated notes") # does not override existing notes if empty test_html = self.render_html( tags_html="""
Example title
Example description """ ) import_netscape_html(test_html, self.get_or_create_test_user(), ImportOptions()) self.assertEqual(Bookmark.objects.count(), 1) self.assertEqual(Bookmark.objects.all()[0].description, "Example description") self.assertEqual(Bookmark.objects.all()[0].notes, "Updated notes") def test_schedule_favicon_loading(self): user = self.get_or_create_test_user() test_html = self.render_html(tags_html="") with patch.object( tasks, "schedule_bookmarks_without_favicons" ) as mock_schedule_bookmarks_without_favicons: import_netscape_html(test_html, user) mock_schedule_bookmarks_without_favicons.assert_called_once_with(user) def test_schedule_preview_loading(self): user = self.get_or_create_test_user() test_html = self.render_html(tags_html="") with patch.object( tasks, "schedule_bookmarks_without_previews" ) as mock_schedule_bookmarks_without_previews: import_netscape_html(test_html, user) mock_schedule_bookmarks_without_previews.assert_called_once_with(user)