import datetime import operator from django.db.models import QuerySet from django.test import TestCase from django.utils import timezone from bookmarks import queries from bookmarks.models import BookmarkBundle, BookmarkSearch, UserProfile from bookmarks.tests.helpers import BookmarkFactoryMixin, random_sentence from bookmarks.utils import unique class QueriesBasicTestCase(TestCase, BookmarkFactoryMixin): def setUp(self): self.profile = self.get_or_create_test_user().profile def setup_bookmark_search_data(self) -> None: tag1 = self.setup_tag(name="tag1") tag2 = self.setup_tag(name="tag2") self.setup_tag(name="unused_tag1") self.other_bookmarks = [ self.setup_bookmark(), self.setup_bookmark(), self.setup_bookmark(), ] self.term1_bookmarks = [ self.setup_bookmark(url="http://example.com/term1"), self.setup_bookmark(title=random_sentence(including_word="term1")), self.setup_bookmark(title=random_sentence(including_word="TERM1")), self.setup_bookmark(description=random_sentence(including_word="term1")), self.setup_bookmark(description=random_sentence(including_word="TERM1")), self.setup_bookmark(notes=random_sentence(including_word="term1")), self.setup_bookmark(notes=random_sentence(including_word="TERM1")), ] self.term1_term2_bookmarks = [ self.setup_bookmark(url="http://example.com/term1/term2"), self.setup_bookmark( title=random_sentence(including_word="term1"), description=random_sentence(including_word="term2"), ), self.setup_bookmark( description=random_sentence(including_word="term1"), title=random_sentence(including_word="term2"), ), ] self.tag1_bookmarks = [ self.setup_bookmark(tags=[tag1]), self.setup_bookmark(title=random_sentence(), tags=[tag1]), self.setup_bookmark(description=random_sentence(), tags=[tag1]), ] self.tag1_as_term_bookmarks = [ self.setup_bookmark(url="http://example.com/tag1"), self.setup_bookmark(title=random_sentence(including_word="tag1")), self.setup_bookmark(description=random_sentence(including_word="tag1")), ] self.term1_tag1_bookmarks = [ self.setup_bookmark(url="http://example.com/term1", tags=[tag1]), self.setup_bookmark( title=random_sentence(including_word="term1"), tags=[tag1] ), self.setup_bookmark( description=random_sentence(including_word="term1"), tags=[tag1] ), ] self.tag2_bookmarks = [ self.setup_bookmark(tags=[tag2]), ] self.tag1_tag2_bookmarks = [ self.setup_bookmark(tags=[tag1, tag2]), ] def setup_tag_search_data(self): tag1 = self.setup_tag(name="tag1") tag2 = self.setup_tag(name="tag2") self.setup_tag(name="unused_tag1") self.other_bookmarks = [ self.setup_bookmark(tags=[self.setup_tag()]), self.setup_bookmark(tags=[self.setup_tag()]), self.setup_bookmark(tags=[self.setup_tag()]), ] self.term1_bookmarks = [ self.setup_bookmark( url="http://example.com/term1", tags=[self.setup_tag()] ), self.setup_bookmark( title=random_sentence(including_word="term1"), tags=[self.setup_tag()] ), self.setup_bookmark( title=random_sentence(including_word="TERM1"), tags=[self.setup_tag()] ), self.setup_bookmark( description=random_sentence(including_word="term1"), tags=[self.setup_tag()], ), self.setup_bookmark( description=random_sentence(including_word="TERM1"), tags=[self.setup_tag()], ), self.setup_bookmark( notes=random_sentence(including_word="term1"), tags=[self.setup_tag()] ), self.setup_bookmark( notes=random_sentence(including_word="TERM1"), tags=[self.setup_tag()] ), ] self.term1_term2_bookmarks = [ self.setup_bookmark( url="http://example.com/term1/term2", tags=[self.setup_tag()] ), self.setup_bookmark( title=random_sentence(including_word="term1"), description=random_sentence(including_word="term2"), tags=[self.setup_tag()], ), self.setup_bookmark( description=random_sentence(including_word="term1"), title=random_sentence(including_word="term2"), tags=[self.setup_tag()], ), ] self.tag1_bookmarks = [ self.setup_bookmark(tags=[tag1, self.setup_tag()]), self.setup_bookmark(title=random_sentence(), tags=[tag1, self.setup_tag()]), self.setup_bookmark( description=random_sentence(), tags=[tag1, self.setup_tag()] ), ] self.tag1_as_term_bookmarks = [ self.setup_bookmark(url="http://example.com/tag1"), self.setup_bookmark(title=random_sentence(including_word="tag1")), self.setup_bookmark(description=random_sentence(including_word="tag1")), ] self.term1_tag1_bookmarks = [ self.setup_bookmark( url="http://example.com/term1", tags=[tag1, self.setup_tag()] ), self.setup_bookmark( title=random_sentence(including_word="term1"), tags=[tag1, self.setup_tag()], ), self.setup_bookmark( description=random_sentence(including_word="term1"), tags=[tag1, self.setup_tag()], ), ] self.tag2_bookmarks = [ self.setup_bookmark(tags=[tag2, self.setup_tag()]), ] self.tag1_tag2_bookmarks = [ self.setup_bookmark(tags=[tag1, tag2, self.setup_tag()]), ] def assertQueryResult(self, query: QuerySet, item_lists: list[list]): expected_items = [] for item_list in item_lists: expected_items = expected_items + item_list expected_items = unique(expected_items, operator.attrgetter("id")) self.assertCountEqual(list(query), expected_items) def test_query_bookmarks_should_return_all_for_empty_query(self): self.setup_bookmark_search_data() query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q="")) self.assertQueryResult( query, [ self.other_bookmarks, self.term1_bookmarks, self.term1_term2_bookmarks, self.tag1_bookmarks, self.tag1_as_term_bookmarks, self.term1_tag1_bookmarks, self.tag2_bookmarks, self.tag1_tag2_bookmarks, ], ) def test_query_bookmarks_should_search_single_term(self): self.setup_bookmark_search_data() query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="term1") ) self.assertQueryResult( query, [ self.term1_bookmarks, self.term1_term2_bookmarks, self.term1_tag1_bookmarks, ], ) def test_query_bookmarks_should_search_multiple_terms(self): self.setup_bookmark_search_data() query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="term2 term1") ) self.assertQueryResult(query, [self.term1_term2_bookmarks]) def test_query_bookmarks_should_search_single_tag(self): self.setup_bookmark_search_data() query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="#tag1") ) self.assertQueryResult( query, [self.tag1_bookmarks, self.tag1_tag2_bookmarks, self.term1_tag1_bookmarks], ) def test_query_bookmarks_should_search_multiple_tags(self): self.setup_bookmark_search_data() query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="#tag1 #tag2") ) self.assertQueryResult(query, [self.tag1_tag2_bookmarks]) def test_query_bookmarks_should_search_multiple_tags_ignoring_casing(self): self.setup_bookmark_search_data() query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="#Tag1 #TAG2") ) self.assertQueryResult(query, [self.tag1_tag2_bookmarks]) def test_query_bookmarks_should_search_terms_and_tags_combined(self): self.setup_bookmark_search_data() query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="term1 #tag1") ) self.assertQueryResult(query, [self.term1_tag1_bookmarks]) def test_query_bookmarks_in_strict_mode_should_not_search_tags_as_terms(self): self.setup_bookmark_search_data() self.profile.tag_search = UserProfile.TAG_SEARCH_STRICT self.profile.save() query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="tag1") ) self.assertQueryResult(query, [self.tag1_as_term_bookmarks]) def test_query_bookmarks_in_lax_mode_should_search_tags_as_terms(self): self.setup_bookmark_search_data() self.profile.tag_search = UserProfile.TAG_SEARCH_LAX self.profile.save() query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="tag1") ) self.assertQueryResult( query, [ self.tag1_bookmarks, self.tag1_as_term_bookmarks, self.tag1_tag2_bookmarks, self.term1_tag1_bookmarks, ], ) query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="tag1 term1") ) self.assertQueryResult( query, [ self.term1_tag1_bookmarks, ], ) query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="tag1 tag2") ) self.assertQueryResult( query, [ self.tag1_tag2_bookmarks, ], ) query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="tag1 #tag2") ) self.assertQueryResult( query, [ self.tag1_tag2_bookmarks, ], ) def test_query_bookmarks_should_return_no_matches(self): self.setup_bookmark_search_data() query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="term3") ) self.assertQueryResult(query, []) query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="term1 term3") ) self.assertQueryResult(query, []) query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="term1 #tag2") ) self.assertQueryResult(query, []) query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="#tag3") ) self.assertQueryResult(query, []) # Unused tag query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="#unused_tag1") ) self.assertQueryResult(query, []) # Unused tag combined with tag that is used query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="#tag1 #unused_tag1") ) self.assertQueryResult(query, []) # Unused tag combined with term that is used query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="term1 #unused_tag1") ) self.assertQueryResult(query, []) def test_query_bookmarks_should_not_return_archived_bookmarks(self): bookmark1 = self.setup_bookmark() bookmark2 = self.setup_bookmark() self.setup_bookmark(is_archived=True) self.setup_bookmark(is_archived=True) self.setup_bookmark(is_archived=True) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q="")) self.assertQueryResult(query, [[bookmark1, bookmark2]]) def test_query_archived_bookmarks_should_not_return_unarchived_bookmarks(self): bookmark1 = self.setup_bookmark(is_archived=True) bookmark2 = self.setup_bookmark(is_archived=True) self.setup_bookmark() self.setup_bookmark() self.setup_bookmark() query = queries.query_archived_bookmarks( self.user, self.profile, BookmarkSearch(q="") ) self.assertQueryResult(query, [[bookmark1, bookmark2]]) def test_query_bookmarks_should_only_return_user_owned_bookmarks(self): other_user = self.setup_user() owned_bookmarks = [ self.setup_bookmark(), self.setup_bookmark(), self.setup_bookmark(), ] self.setup_bookmark(user=other_user) self.setup_bookmark(user=other_user) self.setup_bookmark(user=other_user) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q="")) self.assertQueryResult(query, [owned_bookmarks]) def test_query_archived_bookmarks_should_only_return_user_owned_bookmarks(self): other_user = self.setup_user() owned_bookmarks = [ self.setup_bookmark(is_archived=True), self.setup_bookmark(is_archived=True), self.setup_bookmark(is_archived=True), ] self.setup_bookmark(is_archived=True, user=other_user) self.setup_bookmark(is_archived=True, user=other_user) self.setup_bookmark(is_archived=True, user=other_user) query = queries.query_archived_bookmarks( self.user, self.profile, BookmarkSearch(q="") ) self.assertQueryResult(query, [owned_bookmarks]) def test_query_bookmarks_untagged_should_return_untagged_bookmarks_only(self): tag = self.setup_tag() untagged_bookmark = self.setup_bookmark() self.setup_bookmark(tags=[tag]) self.setup_bookmark(tags=[tag]) query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="!untagged") ) self.assertCountEqual(list(query), [untagged_bookmark]) def test_query_bookmarks_untagged_should_be_combinable_with_search_terms(self): tag = self.setup_tag() untagged_bookmark = self.setup_bookmark(title="term1") self.setup_bookmark(title="term2") self.setup_bookmark(tags=[tag]) query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="!untagged term1") ) self.assertCountEqual(list(query), [untagged_bookmark]) def test_query_bookmarks_untagged_should_not_be_combinable_with_tags(self): tag = self.setup_tag() self.setup_bookmark() self.setup_bookmark(tags=[tag]) self.setup_bookmark(tags=[tag]) query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q=f"!untagged #{tag.name}") ) self.assertCountEqual(list(query), []) def test_query_archived_bookmarks_untagged_should_return_untagged_bookmarks_only( self, ): tag = self.setup_tag() untagged_bookmark = self.setup_bookmark(is_archived=True) self.setup_bookmark(is_archived=True, tags=[tag]) self.setup_bookmark(is_archived=True, tags=[tag]) query = queries.query_archived_bookmarks( self.user, self.profile, BookmarkSearch(q="!untagged") ) self.assertCountEqual(list(query), [untagged_bookmark]) def test_query_archived_bookmarks_untagged_should_be_combinable_with_search_terms( self, ): tag = self.setup_tag() untagged_bookmark = self.setup_bookmark(is_archived=True, title="term1") self.setup_bookmark(is_archived=True, title="term2") self.setup_bookmark(is_archived=True, tags=[tag]) query = queries.query_archived_bookmarks( self.user, self.profile, BookmarkSearch(q="!untagged term1") ) self.assertCountEqual(list(query), [untagged_bookmark]) def test_query_archived_bookmarks_untagged_should_not_be_combinable_with_tags(self): tag = self.setup_tag() self.setup_bookmark(is_archived=True) self.setup_bookmark(is_archived=True, tags=[tag]) self.setup_bookmark(is_archived=True, tags=[tag]) query = queries.query_archived_bookmarks( self.user, self.profile, BookmarkSearch(q=f"!untagged #{tag.name}") ) self.assertCountEqual(list(query), []) def test_query_bookmarks_unread_should_return_unread_bookmarks_only(self): unread_bookmarks = self.setup_numbered_bookmarks(5, unread=True) read_bookmarks = self.setup_numbered_bookmarks(5, unread=False) # Legacy query filter query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="!unread") ) self.assertCountEqual(list(query), unread_bookmarks) # Bookmark search filter - off query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(unread=BookmarkSearch.FILTER_UNREAD_OFF), ) self.assertCountEqual(list(query), read_bookmarks + unread_bookmarks) # Bookmark search filter - yes query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(unread=BookmarkSearch.FILTER_UNREAD_YES), ) self.assertCountEqual(list(query), unread_bookmarks) # Bookmark search filter - no query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(unread=BookmarkSearch.FILTER_UNREAD_NO), ) self.assertCountEqual(list(query), read_bookmarks) def test_query_archived_bookmarks_unread_should_return_unread_bookmarks_only(self): unread_bookmarks = self.setup_numbered_bookmarks(5, unread=True, archived=True) read_bookmarks = self.setup_numbered_bookmarks(5, unread=False, archived=True) # Legacy query filter query = queries.query_archived_bookmarks( self.user, self.profile, BookmarkSearch(q="!unread") ) self.assertCountEqual(list(query), unread_bookmarks) # Bookmark search filter - off query = queries.query_archived_bookmarks( self.user, self.profile, BookmarkSearch(unread=BookmarkSearch.FILTER_UNREAD_OFF), ) self.assertCountEqual(list(query), read_bookmarks + unread_bookmarks) # Bookmark search filter - yes query = queries.query_archived_bookmarks( self.user, self.profile, BookmarkSearch(unread=BookmarkSearch.FILTER_UNREAD_YES), ) self.assertCountEqual(list(query), unread_bookmarks) # Bookmark search filter - no query = queries.query_archived_bookmarks( self.user, self.profile, BookmarkSearch(unread=BookmarkSearch.FILTER_UNREAD_NO), ) self.assertCountEqual(list(query), read_bookmarks) def test_query_bookmarks_filter_shared(self): unshared_bookmarks = self.setup_numbered_bookmarks(5) shared_bookmarks = self.setup_numbered_bookmarks(5, shared=True) # Filter is off search = BookmarkSearch(shared=BookmarkSearch.FILTER_SHARED_OFF) query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), unshared_bookmarks + shared_bookmarks) # Filter for shared search = BookmarkSearch(shared=BookmarkSearch.FILTER_SHARED_SHARED) query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), shared_bookmarks) # Filter for unshared search = BookmarkSearch(shared=BookmarkSearch.FILTER_SHARED_UNSHARED) query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), unshared_bookmarks) def test_query_bookmark_tags_should_return_all_tags_for_empty_query(self): self.setup_tag_search_data() query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="") ) self.assertQueryResult( query, [ self.get_tags_from_bookmarks(self.other_bookmarks), self.get_tags_from_bookmarks(self.term1_bookmarks), self.get_tags_from_bookmarks(self.term1_term2_bookmarks), self.get_tags_from_bookmarks(self.tag1_bookmarks), self.get_tags_from_bookmarks(self.term1_tag1_bookmarks), self.get_tags_from_bookmarks(self.tag2_bookmarks), self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks), ], ) def test_query_bookmark_tags_should_search_single_term(self): self.setup_tag_search_data() query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="term1") ) self.assertQueryResult( query, [ self.get_tags_from_bookmarks(self.term1_bookmarks), self.get_tags_from_bookmarks(self.term1_term2_bookmarks), self.get_tags_from_bookmarks(self.term1_tag1_bookmarks), ], ) def test_query_bookmark_tags_should_search_multiple_terms(self): self.setup_tag_search_data() query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="term2 term1") ) self.assertQueryResult( query, [ self.get_tags_from_bookmarks(self.term1_term2_bookmarks), ], ) def test_query_bookmark_tags_should_search_single_tag(self): self.setup_tag_search_data() query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="#tag1") ) self.assertQueryResult( query, [ self.get_tags_from_bookmarks(self.tag1_bookmarks), self.get_tags_from_bookmarks(self.term1_tag1_bookmarks), self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks), ], ) def test_query_bookmark_tags_should_search_multiple_tags(self): self.setup_tag_search_data() query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="#tag1 #tag2") ) self.assertQueryResult( query, [ self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks), ], ) def test_query_bookmark_tags_should_search_multiple_tags_ignoring_casing(self): self.setup_tag_search_data() query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="#Tag1 #TAG2") ) self.assertQueryResult( query, [ self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks), ], ) def test_query_bookmark_tags_should_search_term_and_tag_combined(self): self.setup_tag_search_data() query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="term1 #tag1") ) self.assertQueryResult( query, [ self.get_tags_from_bookmarks(self.term1_tag1_bookmarks), ], ) def test_query_bookmark_tags_in_strict_mode_should_not_search_tags_as_terms(self): self.setup_tag_search_data() self.profile.tag_search = UserProfile.TAG_SEARCH_STRICT self.profile.save() query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="tag1") ) self.assertQueryResult( query, self.get_tags_from_bookmarks(self.tag1_as_term_bookmarks) ) def test_query_bookmark_tags_in_lax_mode_should_search_tags_as_terms(self): self.setup_tag_search_data() self.profile.tag_search = UserProfile.TAG_SEARCH_LAX self.profile.save() query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="tag1") ) self.assertQueryResult( query, [ self.get_tags_from_bookmarks(self.tag1_bookmarks), self.get_tags_from_bookmarks(self.tag1_as_term_bookmarks), self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks), self.get_tags_from_bookmarks(self.term1_tag1_bookmarks), ], ) query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="tag1 term1") ) self.assertQueryResult( query, [ self.get_tags_from_bookmarks(self.term1_tag1_bookmarks), ], ) query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="tag1 tag2") ) self.assertQueryResult( query, [ self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks), ], ) query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="tag1 #tag2") ) self.assertQueryResult( query, [ self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks), ], ) def test_query_bookmark_tags_should_return_no_matches(self): self.setup_tag_search_data() query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="term3") ) self.assertQueryResult(query, []) query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="term1 term3") ) self.assertQueryResult(query, []) query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="term1 #tag2") ) self.assertQueryResult(query, []) query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="#tag3") ) self.assertQueryResult(query, []) # Unused tag query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="#unused_tag1") ) self.assertQueryResult(query, []) # Unused tag combined with tag that is used query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="#tag1 #unused_tag1") ) self.assertQueryResult(query, []) # Unused tag combined with term that is used query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="term1 #unused_tag1") ) self.assertQueryResult(query, []) def test_query_bookmark_tags_should_return_tags_for_unarchived_bookmarks_only(self): tag1 = self.setup_tag() tag2 = self.setup_tag() self.setup_bookmark(tags=[tag1]) self.setup_bookmark() self.setup_bookmark(is_archived=True, tags=[tag2]) query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="") ) self.assertQueryResult(query, [[tag1]]) def test_query_bookmark_tags_should_return_distinct_tags(self): tag = self.setup_tag() self.setup_bookmark(tags=[tag]) self.setup_bookmark(tags=[tag]) self.setup_bookmark(tags=[tag]) query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="") ) self.assertQueryResult(query, [[tag]]) def test_query_archived_bookmark_tags_should_return_tags_for_archived_bookmarks_only( self, ): tag1 = self.setup_tag() tag2 = self.setup_tag() self.setup_bookmark(tags=[tag1]) self.setup_bookmark() self.setup_bookmark(is_archived=True, tags=[tag2]) query = queries.query_archived_bookmark_tags( self.user, self.profile, BookmarkSearch(q="") ) self.assertQueryResult(query, [[tag2]]) def test_query_archived_bookmark_tags_should_return_distinct_tags(self): tag = self.setup_tag() self.setup_bookmark(is_archived=True, tags=[tag]) self.setup_bookmark(is_archived=True, tags=[tag]) self.setup_bookmark(is_archived=True, tags=[tag]) query = queries.query_archived_bookmark_tags( self.user, self.profile, BookmarkSearch(q="") ) self.assertQueryResult(query, [[tag]]) def test_query_bookmark_tags_should_only_return_user_owned_tags(self): other_user = self.setup_user() owned_bookmarks = [ self.setup_bookmark(tags=[self.setup_tag()]), self.setup_bookmark(tags=[self.setup_tag()]), self.setup_bookmark(tags=[self.setup_tag()]), ] self.setup_bookmark(user=other_user, tags=[self.setup_tag(user=other_user)]) self.setup_bookmark(user=other_user, tags=[self.setup_tag(user=other_user)]) self.setup_bookmark(user=other_user, tags=[self.setup_tag(user=other_user)]) query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="") ) self.assertQueryResult(query, [self.get_tags_from_bookmarks(owned_bookmarks)]) def test_query_archived_bookmark_tags_should_only_return_user_owned_tags(self): other_user = self.setup_user() owned_bookmarks = [ self.setup_bookmark(is_archived=True, tags=[self.setup_tag()]), self.setup_bookmark(is_archived=True, tags=[self.setup_tag()]), self.setup_bookmark(is_archived=True, tags=[self.setup_tag()]), ] self.setup_bookmark( is_archived=True, user=other_user, tags=[self.setup_tag(user=other_user)] ) self.setup_bookmark( is_archived=True, user=other_user, tags=[self.setup_tag(user=other_user)] ) self.setup_bookmark( is_archived=True, user=other_user, tags=[self.setup_tag(user=other_user)] ) query = queries.query_archived_bookmark_tags( self.user, self.profile, BookmarkSearch(q="") ) self.assertQueryResult(query, [self.get_tags_from_bookmarks(owned_bookmarks)]) def test_query_bookmark_tags_untagged_should_never_return_any_tags(self): tag = self.setup_tag() self.setup_bookmark() self.setup_bookmark(title="term1") self.setup_bookmark(title="term1", tags=[tag]) self.setup_bookmark(tags=[tag]) query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="!untagged") ) self.assertCountEqual(list(query), []) query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="!untagged term1") ) self.assertCountEqual(list(query), []) query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q=f"!untagged #{tag.name}") ) self.assertCountEqual(list(query), []) def test_query_archived_bookmark_tags_untagged_should_never_return_any_tags(self): tag = self.setup_tag() self.setup_bookmark(is_archived=True) self.setup_bookmark(is_archived=True, title="term1") self.setup_bookmark(is_archived=True, title="term1", tags=[tag]) self.setup_bookmark(is_archived=True, tags=[tag]) query = queries.query_archived_bookmark_tags( self.user, self.profile, BookmarkSearch(q="!untagged") ) self.assertCountEqual(list(query), []) query = queries.query_archived_bookmark_tags( self.user, self.profile, BookmarkSearch(q="!untagged term1") ) self.assertCountEqual(list(query), []) query = queries.query_archived_bookmark_tags( self.user, self.profile, BookmarkSearch(q=f"!untagged #{tag.name}") ) self.assertCountEqual(list(query), []) def test_query_bookmark_tags_filter_unread(self): unread_bookmarks = self.setup_numbered_bookmarks(5, unread=True, with_tags=True) read_bookmarks = self.setup_numbered_bookmarks(5, unread=False, with_tags=True) unread_tags = self.get_tags_from_bookmarks(unread_bookmarks) read_tags = self.get_tags_from_bookmarks(read_bookmarks) # Legacy query filter query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(q="!unread") ) self.assertCountEqual(list(query), unread_tags) # Bookmark search filter - off query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(unread=BookmarkSearch.FILTER_UNREAD_OFF), ) self.assertCountEqual(list(query), read_tags + unread_tags) # Bookmark search filter - yes query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(unread=BookmarkSearch.FILTER_UNREAD_YES), ) self.assertCountEqual(list(query), unread_tags) # Bookmark search filter - no query = queries.query_bookmark_tags( self.user, self.profile, BookmarkSearch(unread=BookmarkSearch.FILTER_UNREAD_NO), ) self.assertCountEqual(list(query), read_tags) def test_query_bookmark_tags_filter_shared(self): unshared_bookmarks = self.setup_numbered_bookmarks(5, with_tags=True) shared_bookmarks = self.setup_numbered_bookmarks(5, with_tags=True, shared=True) unshared_tags = self.get_tags_from_bookmarks(unshared_bookmarks) shared_tags = self.get_tags_from_bookmarks(shared_bookmarks) all_tags = unshared_tags + shared_tags # Filter is off search = BookmarkSearch(shared=BookmarkSearch.FILTER_SHARED_OFF) query = queries.query_bookmark_tags(self.user, self.profile, search) self.assertCountEqual(list(query), all_tags) # Filter for shared search = BookmarkSearch(shared=BookmarkSearch.FILTER_SHARED_SHARED) query = queries.query_bookmark_tags(self.user, self.profile, search) self.assertCountEqual(list(query), shared_tags) # Filter for unshared search = BookmarkSearch(shared=BookmarkSearch.FILTER_SHARED_UNSHARED) query = queries.query_bookmark_tags(self.user, self.profile, search) self.assertCountEqual(list(query), unshared_tags) def test_query_shared_bookmarks(self): user1 = self.setup_user(enable_sharing=True) user2 = self.setup_user(enable_sharing=True) user3 = self.setup_user(enable_sharing=True) user4 = self.setup_user(enable_sharing=False) tag = self.setup_tag() shared_bookmarks = [ self.setup_bookmark(user=user1, shared=True, title="test title"), self.setup_bookmark(user=user2, shared=True), self.setup_bookmark(user=user3, shared=True, tags=[tag]), ] # Unshared bookmarks self.setup_bookmark(user=user1, shared=False, title="test title") self.setup_bookmark(user=user2, shared=False) self.setup_bookmark(user=user3, shared=False, tags=[tag]) self.setup_bookmark(user=user4, shared=True, tags=[tag]) # Should return shared bookmarks from all users query_set = queries.query_shared_bookmarks( None, self.profile, BookmarkSearch(q=""), False ) self.assertQueryResult(query_set, [shared_bookmarks]) # Should respect search query query_set = queries.query_shared_bookmarks( None, self.profile, BookmarkSearch(q="test title"), False ) self.assertQueryResult(query_set, [[shared_bookmarks[0]]]) query_set = queries.query_shared_bookmarks( None, self.profile, BookmarkSearch(q=f"#{tag.name}"), False ) self.assertQueryResult(query_set, [[shared_bookmarks[2]]]) def test_query_publicly_shared_bookmarks(self): user1 = self.setup_user(enable_sharing=True, enable_public_sharing=True) user2 = self.setup_user(enable_sharing=True) bookmark1 = self.setup_bookmark(user=user1, shared=True) self.setup_bookmark(user=user2, shared=True) query_set = queries.query_shared_bookmarks( None, self.profile, BookmarkSearch(q=""), True ) self.assertQueryResult(query_set, [[bookmark1]]) def test_query_shared_bookmark_tags(self): user1 = self.setup_user(enable_sharing=True) user2 = self.setup_user(enable_sharing=True) user3 = self.setup_user(enable_sharing=True) user4 = self.setup_user(enable_sharing=False) shared_tags = [ self.setup_tag(user=user1), self.setup_tag(user=user2), self.setup_tag(user=user3), ] self.setup_bookmark(user=user1, shared=True, tags=[shared_tags[0]]) self.setup_bookmark(user=user2, shared=True, tags=[shared_tags[1]]) self.setup_bookmark(user=user3, shared=True, tags=[shared_tags[2]]) self.setup_bookmark(user=user1, shared=False, tags=[self.setup_tag(user=user1)]) self.setup_bookmark(user=user2, shared=False, tags=[self.setup_tag(user=user2)]) self.setup_bookmark(user=user3, shared=False, tags=[self.setup_tag(user=user3)]) self.setup_bookmark(user=user4, shared=True, tags=[self.setup_tag(user=user4)]) query_set = queries.query_shared_bookmark_tags( None, self.profile, BookmarkSearch(q=""), False ) self.assertQueryResult(query_set, [shared_tags]) def test_query_publicly_shared_bookmark_tags(self): user1 = self.setup_user(enable_sharing=True, enable_public_sharing=True) user2 = self.setup_user(enable_sharing=True) tag1 = self.setup_tag(user=user1) tag2 = self.setup_tag(user=user2) self.setup_bookmark(user=user1, shared=True, tags=[tag1]) self.setup_bookmark(user=user2, shared=True, tags=[tag2]) query_set = queries.query_shared_bookmark_tags( None, self.profile, BookmarkSearch(q=""), True ) self.assertQueryResult(query_set, [[tag1]]) def test_query_shared_bookmark_users(self): users_with_shared_bookmarks = [ self.setup_user(enable_sharing=True), self.setup_user(enable_sharing=True), ] users_without_shared_bookmarks = [ self.setup_user(enable_sharing=True), self.setup_user(enable_sharing=True), self.setup_user(enable_sharing=False), ] # Shared bookmarks self.setup_bookmark( user=users_with_shared_bookmarks[0], shared=True, title="test title" ) self.setup_bookmark(user=users_with_shared_bookmarks[1], shared=True) # Unshared bookmarks self.setup_bookmark( user=users_without_shared_bookmarks[0], shared=False, title="test title" ) self.setup_bookmark(user=users_without_shared_bookmarks[1], shared=False) self.setup_bookmark(user=users_without_shared_bookmarks[2], shared=True) # Should return users with shared bookmarks query_set = queries.query_shared_bookmark_users( self.profile, BookmarkSearch(q=""), False ) self.assertQueryResult(query_set, [users_with_shared_bookmarks]) # Should respect search query query_set = queries.query_shared_bookmark_users( self.profile, BookmarkSearch(q="test title"), False ) self.assertQueryResult(query_set, [[users_with_shared_bookmarks[0]]]) def test_query_publicly_shared_bookmark_users(self): user1 = self.setup_user(enable_sharing=True, enable_public_sharing=True) user2 = self.setup_user(enable_sharing=True) self.setup_bookmark(user=user1, shared=True) self.setup_bookmark(user=user2, shared=True) query_set = queries.query_shared_bookmark_users( self.profile, BookmarkSearch(q=""), True ) self.assertQueryResult(query_set, [[user1]]) def test_sorty_by_date_added_asc(self): search = BookmarkSearch(sort=BookmarkSearch.SORT_ADDED_ASC) bookmarks = [ self.setup_bookmark( added=timezone.datetime(2020, 1, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( added=timezone.datetime(2021, 2, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( added=timezone.datetime(2022, 3, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( added=timezone.datetime(2023, 4, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( added=timezone.datetime(2022, 5, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( added=timezone.datetime(2021, 6, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( added=timezone.datetime(2020, 7, 1, tzinfo=datetime.UTC) ), ] sorted_bookmarks = sorted(bookmarks, key=lambda b: b.date_added) query = queries.query_bookmarks(self.user, self.profile, search) self.assertEqual(list(query), sorted_bookmarks) def test_sorty_by_date_added_desc(self): search = BookmarkSearch(sort=BookmarkSearch.SORT_ADDED_DESC) bookmarks = [ self.setup_bookmark( added=timezone.datetime(2020, 1, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( added=timezone.datetime(2021, 2, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( added=timezone.datetime(2022, 3, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( added=timezone.datetime(2023, 4, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( added=timezone.datetime(2022, 5, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( added=timezone.datetime(2021, 6, 1, tzinfo=datetime.UTC) ), self.setup_bookmark( added=timezone.datetime(2020, 7, 1, tzinfo=datetime.UTC) ), ] sorted_bookmarks = sorted(bookmarks, key=lambda b: b.date_added, reverse=True) query = queries.query_bookmarks(self.user, self.profile, search) self.assertEqual(list(query), sorted_bookmarks) def setup_title_sort_data(self): # lots of combinations to test effective title logic bookmarks = [ self.setup_bookmark(title="a_1_1"), self.setup_bookmark(title="A_1_2"), self.setup_bookmark(title="b_1_1"), self.setup_bookmark(title="B_1_2"), self.setup_bookmark(title="", url="a_3_1"), self.setup_bookmark(title="", url="A_3_2"), self.setup_bookmark(title="", url="b_3_1"), self.setup_bookmark(title="", url="B_3_2"), self.setup_bookmark(title="a_5_1", url="0"), self.setup_bookmark(title="A_5_2", url="0"), self.setup_bookmark(title="b_5_1", url="0"), self.setup_bookmark(title="B_5_2", url="0"), self.setup_bookmark(title="", url="0"), self.setup_bookmark(title="", url="0"), self.setup_bookmark(title="", url="0"), self.setup_bookmark(title="", url="0"), ] return bookmarks def test_sort_by_title_asc(self): search = BookmarkSearch(sort=BookmarkSearch.SORT_TITLE_ASC) bookmarks = self.setup_title_sort_data() sorted_bookmarks = sorted(bookmarks, key=lambda b: b.resolved_title.lower()) query = queries.query_bookmarks(self.user, self.profile, search) # Use resolved title for comparison as Postgres returns bookmarks with same resolved title in random order expected_effective_titles = [b.resolved_title for b in sorted_bookmarks] actual_effective_titles = [b.resolved_title for b in query] self.assertEqual(expected_effective_titles, actual_effective_titles) def test_sort_by_title_desc(self): search = BookmarkSearch(sort=BookmarkSearch.SORT_TITLE_DESC) bookmarks = self.setup_title_sort_data() sorted_bookmarks = sorted( bookmarks, key=lambda b: b.resolved_title.lower(), reverse=True ) query = queries.query_bookmarks(self.user, self.profile, search) # Use resolved title for comparison as Postgres returns bookmarks with same resolved title in random order expected_effective_titles = [b.resolved_title for b in sorted_bookmarks] actual_effective_titles = [b.resolved_title for b in query] self.assertEqual(expected_effective_titles, actual_effective_titles) def test_query_bookmarks_filter_modified_since(self): # Create bookmarks with different modification dates older_bookmark = self.setup_bookmark(title="old bookmark") recent_bookmark = self.setup_bookmark(title="recent bookmark") # Modify date field on bookmark directly to test modified_since older_bookmark.date_modified = timezone.datetime( 2025, 1, 1, tzinfo=datetime.UTC ) older_bookmark.save() recent_bookmark.date_modified = timezone.datetime( 2025, 5, 15, tzinfo=datetime.UTC ) recent_bookmark.save() # Test with date between the two bookmarks search = BookmarkSearch(modified_since="2025-03-01T00:00:00Z") query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), [recent_bookmark]) # Test with date before both bookmarks search = BookmarkSearch(modified_since="2024-12-31T00:00:00Z") query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), [older_bookmark, recent_bookmark]) # Test with date after both bookmarks search = BookmarkSearch(modified_since="2025-05-16T00:00:00Z") query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), []) # Test with no modified_since - should return all bookmarks search = BookmarkSearch() query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), [older_bookmark, recent_bookmark]) # Test with invalid date format - should be ignored search = BookmarkSearch(modified_since="invalid-date") query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), [older_bookmark, recent_bookmark]) def test_query_bookmarks_filter_added_since(self): # Create bookmarks with different dates older_bookmark = self.setup_bookmark( title="old bookmark", added=timezone.datetime(2025, 1, 1, tzinfo=datetime.UTC), ) recent_bookmark = self.setup_bookmark( title="recent bookmark", added=timezone.datetime(2025, 5, 15, tzinfo=datetime.UTC), ) # Test with date between the two bookmarks search = BookmarkSearch(added_since="2025-03-01T00:00:00Z") query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), [recent_bookmark]) # Test with date before both bookmarks search = BookmarkSearch(added_since="2024-12-31T00:00:00Z") query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), [older_bookmark, recent_bookmark]) # Test with date after both bookmarks search = BookmarkSearch(added_since="2025-05-16T00:00:00Z") query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), []) # Test with no added_since - should return all bookmarks search = BookmarkSearch() query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), [older_bookmark, recent_bookmark]) # Test with invalid date format - should be ignored search = BookmarkSearch(added_since="invalid-date") query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), [older_bookmark, recent_bookmark]) def test_query_bookmarks_with_bundle_search_terms(self): bundle = self.setup_bundle(search="search_term_A search_term_B") matching_bookmarks = [ self.setup_bookmark( title="search_term_A content", description="search_term_B also here" ), self.setup_bookmark(url="http://example.com/search_term_A/search_term_B"), ] # Bookmarks that should not match self.setup_bookmark(title="search_term_A only") self.setup_bookmark(description="search_term_B only") self.setup_bookmark(title="unrelated content") query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="", bundle=bundle) ) self.assertQueryResult(query, [matching_bookmarks]) def test_query_bookmarks_with_search_and_bundle_search_terms(self): bundle = self.setup_bundle(search="bundle_term_B") search = BookmarkSearch(q="search_term_A", bundle=bundle) matching_bookmarks = [ self.setup_bookmark( title="search_term_A content", description="bundle_term_B also here" ) ] # Bookmarks that should not match self.setup_bookmark(title="search_term_A only") self.setup_bookmark(description="bundle_term_B only") self.setup_bookmark(title="unrelated content") query = queries.query_bookmarks(self.user, self.profile, search) self.assertQueryResult(query, [matching_bookmarks]) def test_query_bookmarks_with_bundle_any_tags(self): bundle = self.setup_bundle(any_tags="bundleTag1 bundleTag2") tag1 = self.setup_tag(name="bundleTag1") tag2 = self.setup_tag(name="bundleTag2") other_tag = self.setup_tag(name="otherTag") matching_bookmarks = [ self.setup_bookmark(tags=[tag1]), self.setup_bookmark(tags=[tag2]), self.setup_bookmark(tags=[tag1, tag2]), ] # Bookmarks that should not match self.setup_bookmark(tags=[other_tag]) self.setup_bookmark() query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="", bundle=bundle) ) self.assertQueryResult(query, [matching_bookmarks]) def test_query_bookmarks_with_search_tags_and_bundle_any_tags(self): bundle = self.setup_bundle(any_tags="bundleTagA bundleTagB") search = BookmarkSearch(q="#searchTag1 #searchTag2", bundle=bundle) search_tag1 = self.setup_tag(name="searchTag1") search_tag2 = self.setup_tag(name="searchTag2") bundle_tag_a = self.setup_tag(name="bundleTagA") bundle_tag_b = self.setup_tag(name="bundleTagB") other_tag = self.setup_tag(name="otherTag") matching_bookmarks = [ self.setup_bookmark(tags=[search_tag1, search_tag2, bundle_tag_a]), self.setup_bookmark(tags=[search_tag1, search_tag2, bundle_tag_b]), self.setup_bookmark( tags=[search_tag1, search_tag2, bundle_tag_a, bundle_tag_b] ), ] # Bookmarks that should not match self.setup_bookmark(tags=[search_tag1, search_tag2, other_tag]) self.setup_bookmark(tags=[search_tag1, search_tag2]) self.setup_bookmark(tags=[search_tag1, bundle_tag_a]) self.setup_bookmark(tags=[search_tag2, bundle_tag_b]) self.setup_bookmark(tags=[bundle_tag_a]) self.setup_bookmark(tags=[bundle_tag_b]) self.setup_bookmark(tags=[bundle_tag_a, bundle_tag_b]) self.setup_bookmark(tags=[other_tag]) self.setup_bookmark() query = queries.query_bookmarks(self.user, self.profile, search) self.assertQueryResult(query, [matching_bookmarks]) def test_query_bookmarks_with_bundle_all_tags(self): bundle = self.setup_bundle(all_tags="bundleTag1 bundleTag2") tag1 = self.setup_tag(name="bundleTag1") tag2 = self.setup_tag(name="bundleTag2") other_tag = self.setup_tag(name="otherTag") matching_bookmarks = [self.setup_bookmark(tags=[tag1, tag2])] # Bookmarks that should not match self.setup_bookmark(tags=[tag1]) self.setup_bookmark(tags=[tag2]) self.setup_bookmark(tags=[tag1, other_tag]) self.setup_bookmark(tags=[other_tag]) self.setup_bookmark() query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="", bundle=bundle) ) self.assertQueryResult(query, [matching_bookmarks]) def test_query_bookmarks_with_search_tags_and_bundle_all_tags(self): bundle = self.setup_bundle(all_tags="bundleTagA bundleTagB") search = BookmarkSearch(q="#searchTag1 #searchTag2", bundle=bundle) search_tag1 = self.setup_tag(name="searchTag1") search_tag2 = self.setup_tag(name="searchTag2") bundle_tag_a = self.setup_tag(name="bundleTagA") bundle_tag_b = self.setup_tag(name="bundleTagB") other_tag = self.setup_tag(name="otherTag") matching_bookmarks = [ self.setup_bookmark( tags=[search_tag1, search_tag2, bundle_tag_a, bundle_tag_b] ) ] # Bookmarks that should not match self.setup_bookmark(tags=[search_tag1, search_tag2, bundle_tag_a]) self.setup_bookmark(tags=[search_tag1, bundle_tag_a, bundle_tag_b]) self.setup_bookmark(tags=[search_tag1, search_tag2]) self.setup_bookmark(tags=[bundle_tag_a, bundle_tag_b]) self.setup_bookmark(tags=[search_tag1, bundle_tag_a]) self.setup_bookmark(tags=[other_tag]) self.setup_bookmark() query = queries.query_bookmarks(self.user, self.profile, search) self.assertQueryResult(query, [matching_bookmarks]) def test_query_bookmarks_with_bundle_excluded_tags(self): bundle = self.setup_bundle(excluded_tags="excludeTag1 excludeTag2") exclude_tag1 = self.setup_tag(name="excludeTag1") exclude_tag2 = self.setup_tag(name="excludeTag2") keep_tag = self.setup_tag(name="keepTag") keep_other_tag = self.setup_tag(name="keepOtherTag") matching_bookmarks = [ self.setup_bookmark(tags=[keep_tag]), self.setup_bookmark(tags=[keep_other_tag]), self.setup_bookmark(tags=[keep_tag, keep_other_tag]), self.setup_bookmark(), ] # Bookmarks that should not be returned self.setup_bookmark(tags=[exclude_tag1]) self.setup_bookmark(tags=[exclude_tag2]) self.setup_bookmark(tags=[exclude_tag1, keep_tag]) self.setup_bookmark(tags=[exclude_tag2, keep_tag]) self.setup_bookmark(tags=[exclude_tag1, exclude_tag2]) self.setup_bookmark(tags=[exclude_tag1, exclude_tag2, keep_tag]) query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="", bundle=bundle) ) self.assertQueryResult(query, [matching_bookmarks]) def test_query_bookmarks_with_bundle_combined_tags(self): bundle = self.setup_bundle( any_tags="anyTagA anyTagB", all_tags="allTag1 allTag2", excluded_tags="excludedTag", ) any_tag_a = self.setup_tag(name="anyTagA") any_tag_b = self.setup_tag(name="anyTagB") all_tag_1 = self.setup_tag(name="allTag1") all_tag_2 = self.setup_tag(name="allTag2") other_tag = self.setup_tag(name="otherTag") excluded_tag = self.setup_tag(name="excludedTag") matching_bookmarks = [ self.setup_bookmark(tags=[any_tag_a, all_tag_1, all_tag_2]), self.setup_bookmark(tags=[any_tag_b, all_tag_1, all_tag_2]), self.setup_bookmark(tags=[any_tag_a, any_tag_b, all_tag_1, all_tag_2]), self.setup_bookmark(tags=[any_tag_a, all_tag_1, all_tag_2, other_tag]), self.setup_bookmark(tags=[any_tag_b, all_tag_1, all_tag_2, other_tag]), ] # Bookmarks that should not match self.setup_bookmark(tags=[any_tag_a, all_tag_1]) self.setup_bookmark(tags=[any_tag_b, all_tag_2]) self.setup_bookmark(tags=[any_tag_a, any_tag_b, all_tag_1]) self.setup_bookmark(tags=[all_tag_1, all_tag_2]) self.setup_bookmark(tags=[all_tag_1, all_tag_2, other_tag]) self.setup_bookmark(tags=[any_tag_a]) self.setup_bookmark(tags=[any_tag_b]) self.setup_bookmark(tags=[all_tag_1]) self.setup_bookmark(tags=[all_tag_2]) self.setup_bookmark(tags=[any_tag_a, all_tag_1, all_tag_2, excluded_tag]) self.setup_bookmark(tags=[any_tag_b, all_tag_1, all_tag_2, excluded_tag]) self.setup_bookmark(tags=[other_tag]) self.setup_bookmark() query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="", bundle=bundle) ) self.assertQueryResult(query, [matching_bookmarks]) def test_query_bookmarks_with_bundle_filter_unread(self): unread_bookmarks = [ self.setup_bookmark(unread=True), self.setup_bookmark(unread=True), ] read_bookmarks = [ self.setup_bookmark(unread=False), self.setup_bookmark(unread=False), ] # Filter unread bundle = self.setup_bundle(filter_unread=BookmarkBundle.FILTER_STATE_YES) query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="", bundle=bundle) ) self.assertQueryResult(query, [unread_bookmarks]) # Filter read bundle = self.setup_bundle(filter_unread=BookmarkBundle.FILTER_STATE_NO) query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="", bundle=bundle) ) self.assertQueryResult(query, [read_bookmarks]) # Filter off bundle = self.setup_bundle(filter_unread=BookmarkBundle.FILTER_STATE_OFF) query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="", bundle=bundle) ) self.assertQueryResult(query, [unread_bookmarks, read_bookmarks]) def test_query_bookmarks_with_bundle_filter_shared(self): shared_bookmarks = [ self.setup_bookmark(shared=True), self.setup_bookmark(shared=True), ] unshared_bookmarks = [ self.setup_bookmark(shared=False), self.setup_bookmark(shared=False), ] # Filter shared bundle = self.setup_bundle(filter_shared=BookmarkBundle.FILTER_STATE_YES) query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="", bundle=bundle) ) self.assertQueryResult(query, [shared_bookmarks]) # Filter unshared bundle = self.setup_bundle(filter_shared=BookmarkBundle.FILTER_STATE_NO) query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="", bundle=bundle) ) self.assertQueryResult(query, [unshared_bookmarks]) # Filter off bundle = self.setup_bundle(filter_shared=BookmarkBundle.FILTER_STATE_OFF) query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="", bundle=bundle) ) self.assertQueryResult(query, [shared_bookmarks, unshared_bookmarks]) def test_query_bookmarks_with_bundle_unread_shared_filters_combined(self): bundle = self.setup_bundle( search="python", filter_unread=BookmarkBundle.FILTER_STATE_YES, filter_shared=BookmarkBundle.FILTER_STATE_NO, ) matching_bookmarks = [ self.setup_bookmark(title="Python Tutorial", unread=True, shared=False), ] # Bookmarks that should not match self.setup_bookmark(title="Python Guide", unread=False, shared=False) self.setup_bookmark(title="Python Docs", unread=True, shared=True) self.setup_bookmark(title="Java Guide", unread=True, shared=False) query = queries.query_bookmarks( self.user, self.profile, BookmarkSearch(q="", bundle=bundle) ) self.assertQueryResult(query, [matching_bookmarks]) def test_query_archived_bookmarks_with_bundle(self): bundle = self.setup_bundle(any_tags="bundleTag1 bundleTag2") tag1 = self.setup_tag(name="bundleTag1") tag2 = self.setup_tag(name="bundleTag2") other_tag = self.setup_tag(name="otherTag") matching_bookmarks = [ self.setup_bookmark(is_archived=True, tags=[tag1]), self.setup_bookmark(is_archived=True, tags=[tag2]), self.setup_bookmark(is_archived=True, tags=[tag1, tag2]), ] # Bookmarks that should not match self.setup_bookmark(is_archived=True, tags=[other_tag]) self.setup_bookmark(is_archived=True) self.setup_bookmark(tags=[tag1]) self.setup_bookmark(tags=[tag2]) self.setup_bookmark(tags=[tag1, tag2]) query = queries.query_archived_bookmarks( self.user, self.profile, BookmarkSearch(q="", bundle=bundle) ) self.assertQueryResult(query, [matching_bookmarks]) def test_query_shared_bookmarks_with_bundle(self): user1 = self.setup_user(enable_sharing=True) user2 = self.setup_user(enable_sharing=True) bundle = self.setup_bundle(any_tags="bundleTag1 bundleTag2") tag1 = self.setup_tag(name="bundleTag1") tag2 = self.setup_tag(name="bundleTag2") other_tag = self.setup_tag(name="otherTag") matching_bookmarks = [ self.setup_bookmark(user=user1, shared=True, tags=[tag1]), self.setup_bookmark(user=user2, shared=True, tags=[tag2]), self.setup_bookmark(user=user1, shared=True, tags=[tag1, tag2]), ] # Bookmarks that should not match self.setup_bookmark(user=user1, shared=True, tags=[other_tag]) self.setup_bookmark(user=user2, shared=True) self.setup_bookmark(user=user1, shared=False, tags=[tag1]) self.setup_bookmark(user=user2, shared=False, tags=[tag2]) self.setup_bookmark(user=user1, shared=False, tags=[tag1, tag2]) query = queries.query_shared_bookmarks( None, self.profile, BookmarkSearch(q="", bundle=bundle), False ) self.assertQueryResult(query, [matching_bookmarks]) # Legacy search should be covered by basic test suite which was effectively the # full test suite before advanced search was introduced. class QueriesLegacySearchTestCase(QueriesBasicTestCase): def setUp(self): super().setUp() self.profile.legacy_search = True self.profile.save() class QueriesAdvancedSearchTestCase(TestCase, BookmarkFactoryMixin): def setUp(self): self.user = self.get_or_create_test_user() self.profile = self.user.profile self.python_bookmark = self.setup_bookmark( title="Python Tutorial", tags=[self.setup_tag(name="python"), self.setup_tag(name="tutorial")], ) self.java_bookmark = self.setup_bookmark( title="Java Guide", tags=[self.setup_tag(name="java"), self.setup_tag(name="programming")], ) self.deprecated_python_bookmark = self.setup_bookmark( title="Old Python Guide", tags=[self.setup_tag(name="python"), self.setup_tag(name="deprecated")], ) self.javascript_tutorial = self.setup_bookmark( title="JavaScript Basics", tags=[self.setup_tag(name="javascript"), self.setup_tag(name="tutorial")], ) self.web_development = self.setup_bookmark( title="Web Development with React", description="Modern web development", tags=[self.setup_tag(name="react"), self.setup_tag(name="web")], ) def test_explicit_and_operator(self): search = BookmarkSearch(q="python AND tutorial") query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), [self.python_bookmark]) def test_or_operator(self): search = BookmarkSearch(q="#python OR #java") query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual( list(query), [self.python_bookmark, self.java_bookmark, self.deprecated_python_bookmark], ) def test_not_operator(self): search = BookmarkSearch(q="#python AND NOT #deprecated") query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), [self.python_bookmark]) def test_implicit_and_between_terms(self): search = BookmarkSearch(q="web development") query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), [self.web_development]) search = BookmarkSearch(q="python tutorial") query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), [self.python_bookmark]) def test_implicit_and_between_tags(self): search = BookmarkSearch(q="#python #tutorial") query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), [self.python_bookmark]) def test_nested_and_expression(self): search = BookmarkSearch(q="nonexistingterm OR (#python AND #tutorial)") query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), [self.python_bookmark]) search = BookmarkSearch( q="(#javascript AND #tutorial) OR (#python AND #tutorial)" ) query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual( list(query), [self.javascript_tutorial, self.python_bookmark] ) def test_mixed_terms_and_tags_with_operators(self): # Set lax mode to allow term matching against tags self.profile.tag_search = self.profile.TAG_SEARCH_LAX self.profile.save() search = BookmarkSearch(q="(tutorial OR guide) AND #python") query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual( list(query), [self.python_bookmark, self.deprecated_python_bookmark] ) def test_parentheses(self): # Set lax mode to allow term matching against tags self.profile.tag_search = self.profile.TAG_SEARCH_LAX self.profile.save() # Without parentheses search = BookmarkSearch(q="python AND tutorial OR javascript AND tutorial") query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual( list(query), [self.python_bookmark, self.javascript_tutorial] ) # With parentheses search = BookmarkSearch(q="(python OR javascript) AND tutorial") query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual( list(query), [self.python_bookmark, self.javascript_tutorial] ) def test_complex_query_with_all_operators(self): # Set lax mode to allow term matching against tags self.profile.tag_search = self.profile.TAG_SEARCH_LAX self.profile.save() search = BookmarkSearch( q="(#python OR #javascript) AND tutorial AND NOT #deprecated" ) query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual( list(query), [self.python_bookmark, self.javascript_tutorial] ) def test_quoted_strings_with_operators(self): # Set lax mode to allow term matching against tags self.profile.tag_search = self.profile.TAG_SEARCH_LAX self.profile.save() search = BookmarkSearch(q='"Web Development" OR tutorial') query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual( list(query), [self.web_development, self.python_bookmark, self.javascript_tutorial], ) def test_implicit_and_with_quoted_strings(self): search = BookmarkSearch(q='"Web Development" react') query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), [self.web_development]) def test_empty_query(self): # empty query returns all bookmarks search = BookmarkSearch(q="") query = queries.query_bookmarks(self.user, self.profile, search) expected = [ self.python_bookmark, self.java_bookmark, self.deprecated_python_bookmark, self.javascript_tutorial, self.web_development, ] self.assertCountEqual(list(query), expected) def test_unparseable_query_returns_no_results(self): # Use a query that causes a parse error (unclosed parenthesis) search = BookmarkSearch(q="(python AND tutorial") query = queries.query_bookmarks(self.user, self.profile, search) self.assertCountEqual(list(query), []) class GetTagsForQueryTestCase(TestCase, BookmarkFactoryMixin): def setUp(self): self.user = self.get_or_create_test_user() self.profile = self.user.profile def test_returns_tags_matching_query(self): python_tag = self.setup_tag(name="python") django_tag = self.setup_tag(name="django") self.setup_tag(name="unused") result = queries.get_tags_for_query( self.user, self.profile, "#python and #django" ) self.assertCountEqual(list(result), [python_tag, django_tag]) def test_case_insensitive_matching(self): python_tag = self.setup_tag(name="Python") result = queries.get_tags_for_query(self.user, self.profile, "#python") self.assertCountEqual(list(result), [python_tag]) # having two tags with the same name returns both for now other_python_tag = self.setup_tag(name="python") result = queries.get_tags_for_query(self.user, self.profile, "#python") self.assertCountEqual(list(result), [python_tag, other_python_tag]) def test_lax_mode_includes_terms(self): python_tag = self.setup_tag(name="python") django_tag = self.setup_tag(name="django") self.profile.tag_search = UserProfile.TAG_SEARCH_LAX self.profile.save() result = queries.get_tags_for_query( self.user, self.profile, "#python and django" ) self.assertCountEqual(list(result), [python_tag, django_tag]) def test_strict_mode_excludes_terms(self): python_tag = self.setup_tag(name="python") self.setup_tag(name="django") result = queries.get_tags_for_query( self.user, self.profile, "#python and django" ) self.assertCountEqual(list(result), [python_tag]) def test_only_returns_user_tags(self): python_tag = self.setup_tag(name="python") other_user = self.setup_user() other_python = self.setup_tag(name="python", user=other_user) other_django = self.setup_tag(name="django", user=other_user) result = queries.get_tags_for_query( self.user, self.profile, "#python and #django" ) self.assertCountEqual(list(result), [python_tag]) self.assertNotIn(other_python, list(result)) self.assertNotIn(other_django, list(result)) def test_empty_query_returns_no_tags(self): self.setup_tag(name="python") result = queries.get_tags_for_query(self.user, self.profile, "") self.assertCountEqual(list(result), []) def test_query_with_no_tags_returns_empty(self): self.setup_tag(name="python") result = queries.get_tags_for_query(self.user, self.profile, "!unread") self.assertCountEqual(list(result), []) def test_nonexistent_tag_returns_empty(self): self.setup_tag(name="python") result = queries.get_tags_for_query(self.user, self.profile, "#ruby") self.assertCountEqual(list(result), []) class GetSharedTagsForQueryTestCase(TestCase, BookmarkFactoryMixin): def setUp(self): self.user = self.get_or_create_test_user() self.profile = self.user.profile self.profile.enable_sharing = True self.profile.save() def test_returns_tags_from_shared_bookmarks(self): python_tag = self.setup_tag(name="python") self.setup_tag(name="django") self.setup_bookmark(shared=True, tags=[python_tag]) result = queries.get_shared_tags_for_query( None, self.profile, "#python and #django", public_only=False ) self.assertCountEqual(list(result), [python_tag]) def test_excludes_tags_from_non_shared_bookmarks(self): python_tag = self.setup_tag(name="python") self.setup_tag(name="django") self.setup_bookmark(shared=False, tags=[python_tag]) result = queries.get_shared_tags_for_query( None, self.profile, "#python and #django", public_only=False ) self.assertCountEqual(list(result), []) def test_respects_sharing_enabled_setting(self): self.profile.enable_sharing = False self.profile.save() python_tag = self.setup_tag(name="python") self.setup_tag(name="django") self.setup_bookmark(shared=True, tags=[python_tag]) result = queries.get_shared_tags_for_query( None, self.profile, "#python and #django", public_only=False ) self.assertCountEqual(list(result), []) def test_public_only_flag(self): # public sharing disabled python_tag = self.setup_tag(name="python") self.setup_tag(name="django") self.setup_bookmark(shared=True, tags=[python_tag]) result = queries.get_shared_tags_for_query( None, self.profile, "#python and #django", public_only=True ) self.assertCountEqual(list(result), []) # public sharing enabled self.profile.enable_public_sharing = True self.profile.save() result = queries.get_shared_tags_for_query( None, self.profile, "#python and #django", public_only=True ) self.assertCountEqual(list(result), [python_tag]) def test_filters_by_user(self): python_tag = self.setup_tag(name="python") self.setup_tag(name="django") self.setup_bookmark(shared=True, tags=[python_tag]) other_user = self.setup_user() other_user.profile.enable_sharing = True other_user.profile.save() other_tag = self.setup_tag(name="python", user=other_user) self.setup_bookmark(shared=True, tags=[other_tag], user=other_user) result = queries.get_shared_tags_for_query( self.user, self.profile, "#python and #django", public_only=False ) self.assertCountEqual(list(result), [python_tag]) self.assertNotIn(other_tag, list(result))