diff --git a/README.md b/README.md index d79ae84..920ef6b 100644 --- a/README.md +++ b/README.md @@ -189,9 +189,12 @@ SEARCH OPTIONS: "immutable": entries with locked title --deep match substrings ('pen' matches 'opens') -r, --sreg run a regex search - -t, --stag search bookmarks by a tag + -t, --stag [tag [,|+] ...] [- tag, ...] + search bookmarks by tags + use ',' to find entries matching ANY tag + use '+' to find entries matching ALL tags + excludes entries matching tags following ' - ' list all tags, if no search keywords - ENCRYPTION OPTIONS: -l, --lock [N] encrypt DB file with N (> 0, default 8) hash iterations to generate key @@ -242,7 +245,7 @@ PROMPT KEYS: S keyword [...] search for records with ALL keywords d match substrings ('pen' matches 'opened') r expression run a regex search - t [...] search bookmarks by a tag or show taglist + t [...] search bookmarks by tags or show taglist list index after a tag listing shows records with the tag o id|range [...] browse bookmarks by indices and/or ranges p id|range [...] print bookmarks by indices and/or ranges @@ -284,7 +287,7 @@ PROMPT KEYS: - --sall : match all the keywords in URL, title or tags. - --deep : match **substrings** (`match` matches `rematched`) in URL, title and tags. - --sreg : match a regular expression (ignores --deep). - - --stag : search bookmarks by a tag, or list all tags alphabetically with usage count (if no arguments). + - --stag : search bookmarks by tags, or list all tags alphabetically with usage count (if no arguments). Delimit the list of tags in the query with `,` to search for bookmarks that match ANY of the listed tags. Delimit tags with `+` to search for bookmarks that match ALL of the listed tags. Note that `,` and `+` cannot be used together in the same search. Exclude bookmarks matching certain tags from the results by using ` - ` followed by the tags. Note that the ` - ` operator and the ` + ` delimiter must be space separated: ` - ` instead of `-` and ` + ` instead of `+`. This is to distinguish them from hyphenated tags (e.g., `some-tag-name`) and tags with '+'s (e.g., `some+tag+name`). - Search results are indexed serially. This index is different from actual database index of a bookmark record which is shown within `[]` after the title. - **Import**: - URLs starting with `place:`, `file://` and `apt:` are ignored during import. @@ -498,54 +501,63 @@ for resp, url in zip(gr_results, urls): 18. **Search** bookmarks **tagged** `general kernel concepts`: $ buku --stag general kernel concepts -19. List **all unique tags** alphabetically: +19. **Search** for bookmarks matching **ANY** of the tags `kernel`, `debugging`, `general kernel concepts`: + + $ buku --stag kernel, debugging, general kernel concepts +20. **Search** for bookmarks matching **ALL** of the tags `kernel`, `debugging`, `general kernel concepts`: + + $ buku --stag kernel + debugging + general kernel concepts +21. **Search** for bookmarks matching both the tags `kernel` and `debugging`, but **excluding** bookmarks matching the tag `general kernel concepts`: + + $ buku --stag kernel + debugging - general kernel concepts +22. List **all unique tags** alphabetically: $ buku --stag -20. Run a **search and update** the results: +23. Run a **search and update** the results: $ buku -s kernel debugging -u --tag + linux kernel -21. Run a **search and delete** the results: +24. Run a **search and delete** the results: $ buku -s kernel debugging -d -22. **Encrypt or decrypt** DB with **custom number of iterations** (15) to generate key: +25. **Encrypt or decrypt** DB with **custom number of iterations** (15) to generate key: $ buku -l 15 $ buku -k 15 The same number of iterations must be specified for one lock & unlock instance. Default is 8, if omitted. -23. **Show details** of bookmarks at index 15012014 and ranges 20-30, 40-50: +26. **Show details** of bookmarks at index 15012014 and ranges 20-30, 40-50: $ buku -p 20-30 15012014 40-50 -24. Show details of the **last 10 bookmarks**: +27. Show details of the **last 10 bookmarks**: $ buku -p -10 -25. **Show all** bookmarks with real index from database: +28. **Show all** bookmarks with real index from database: $ buku -p $ buku -p | more -26. **Replace tag** 'old tag' with 'new tag': +29. **Replace tag** 'old tag' with 'new tag': $ buku --replace 'old tag' 'new tag' -27. **Delete tag** 'old tag' from DB: +30. **Delete tag** 'old tag' from DB: $ buku --replace 'old tag' -28. **Append (or delete) tags** 'tag 1', 'tag 2' to (or from) existing tags of bookmark at index 15012014: +31. **Append (or delete) tags** 'tag 1', 'tag 2' to (or from) existing tags of bookmark at index 15012014: $ buku -u 15012014 --tag + tag 1, tag 2 $ buku -u 15012014 --tag - tag 1, tag 2 -29. **Open URL** at index 15012014 in browser: +32. **Open URL** at index 15012014 in browser: $ buku -o 15012014 -30. List bookmarks with **no title or tags** for bookkeeping: +33. List bookmarks with **no title or tags** for bookkeeping: $ buku -S blank -31. List bookmarks with **immutable title**: +34. List bookmarks with **immutable title**: $ buku -S immutable -32. **Shorten URL** www.google.com and the URL at index 20: +35. **Shorten URL** www.google.com and the URL at index 20: $ buku --shorten www.google.com $ buku --shorten 20 -33. **Append, remove tags at prompt** (taglist index to the left, bookmark index to the right): +36. **Append, remove tags at prompt** (taglist index to the left, bookmark index to the right): // append tags at taglist indices 4 and 6-9 to existing tags in bookmarks at indices 5 and 2-3 buku (? for help) g 4 9-6 >> 5 3-2 @@ -555,7 +567,7 @@ for resp, url in zip(gr_results, urls): buku (? for help) g > 5 3-2 // remove tags at taglist indices 4 and 6-9 from tags in bookmarks at indices 5 and 2-3 buku (? for help) g 4 9-6 << 5 3-2 -34. More **help**: +37. More **help**: $ buku -h $ man buku diff --git a/buku.1 b/buku.1 index 4958b20..54b170f 100644 --- a/buku.1 +++ b/buku.1 @@ -69,7 +69,7 @@ Bookmarks with immutable titles are listed with '(L)' after the title. - --sall : match all the keywords in URL, title or tags. - --deep : match \fBsubstrings\fR (`match` matches `rematched`) in URL, title and tags. - --sreg : match a regular expression (ignores --deep). - - --stag : search bookmarks by a tag, or list all tags alphabetically with usage count (if no arguments). + - --stag : search bookmarks by tags, or list all tags alphabetically with usage count (if no arguments). Delimit the list of tags in the query with `,` to search for bookmarks that match ANY of the listed tags. Delimit tags with `+` to search for bookmarks that match ALL of the listed tags. Note that `,` and `+` cannot be used together in the same search. Exclude bookmarks matching certain tags from the results by using ` - ` followed by the tags. Note that the ` - ` operator and the ` + ` delimiter must be space separated: ` - ` instead of `-` and ` + ` instead of `+`. This is to distinguish them from hyphenated tags (e.g., `some-tag-name`) and tags with '+'s (e.g., `some+tag+name`). - Search results are indexed serially. This index is different from actual database index of a bookmark record which is shown within '[]' after the title. .PP .IP 9. 4 @@ -154,8 +154,18 @@ Search modifier to match substrings. Works with --sany, --sall. .BI \-r " " \--sreg " expression" Scan for a regular expression match. .TP -.BI \-t " " \--stag " [...]" -Search bookmarks by a tag. List all tags alphabetically, if no arguments. The usage count (number of bookmarks having the tag) is shown within first brackets. +.BI \-t " " \--stag " [tag [,|+] ...] [\- tag, ...]" +Search bookmarks by tags. +.br +Use ',' delimiter to find entries matching ANY of the tags +.br +Use ' + ' delimiter to find entries matching ALL of the tags. (Note that the ' + ' delimiter must be space separated) +.br +NOTE: Cannot combine ',' and '+' in the same search +.br +Use ' - ' to exclude bookmarks that match the tags that follow. (Note that the '-' operator must be space separated). +.br +List all tags alphabetically, if no arguments. The usage count (number of bookmarks having the tag) is shown within first brackets. .SH ENCRYPTION OPTIONS .TP .BI \-l " " \--lock " [N]" @@ -506,6 +516,28 @@ The last index is moved to the deleted index to keep the DB compact. .EE .PP .IP 19. 4 +\fBSearch\fR for bookmarks matching \fBANY\fR of the tags 'kernel', 'debugging', 'general kernel concepts': +.PP +.EX +.IP +.B buku --stag kernel, debugging, general kernel concepts +.EE +.PP +.IP 20.4 +\fBSearch\fR for bookmarks matching \fBALL\fR of the tags 'kernel', 'debugging', 'general kernel concepts': +.PP +.EX +.IP +.B buku --stag kernel + debugging + general kernel concepts +.EE +.PP +.IP 21.4 +\fBSearch\fR for bookmarks matching both the tags `kernel` and `debugging`, but \fBexcluding\fR bookmarks matching the tag 'general kernel concepts': +.PP +.EX +.IP +.B buku --stag kernel + debugging - general kernel concepts +.IP 22.4 List \fBall unique tags\fR alphabetically: .PP .EX @@ -513,7 +545,7 @@ List \fBall unique tags\fR alphabetically: .B buku --stag .EE .PP -.IP 20. 4 +.IP 23. 4 Run a \fBsearch and update\fR the results: .PP .EX @@ -521,7 +553,7 @@ Run a \fBsearch and update\fR the results: .B buku -s kernel debugging -u --tag + linux kernel .EE .PP -.IP 21. 4 +.IP 24. 4 Run a \fBsearch and delete\fR the results: .PP .EX @@ -529,7 +561,7 @@ Run a \fBsearch and delete\fR the results: .B buku -s kernel debugging -d .EE .PP -.IP 22. 4 +.IP 25. 4 \fBEncrypt or decrypt\fR DB with \fBcustom number of iterations\fR (15) to generate key: .PP .EX @@ -542,7 +574,7 @@ Run a \fBsearch and delete\fR the results: .IP "" 4 The same number of iterations must be specified for one lock & unlock instance. Default is 8, if omitted. .PP -.IP 23. 4 +.IP 26. 4 \fBShow details\fR of bookmarks at index 15012014 and ranges 20-30, 40-50: .PP .EX @@ -550,7 +582,7 @@ The same number of iterations must be specified for one lock & unlock instance. .B buku -p 20-30 15012014 40-50 .EE .PP -.IP 24. 4 +.IP 27. 4 Show details of the \fBlast 10 bookmarks\fR: .PP .EX @@ -558,7 +590,7 @@ Show details of the \fBlast 10 bookmarks\fR: .B buku -p -10 .EE .PP -.IP 25. 4 +.IP 28. 4 \fBShow all\fR bookmarks with real index from database: .PP .EX @@ -567,7 +599,7 @@ Show details of the \fBlast 10 bookmarks\fR: .B buku -p | more .EE .PP -.IP 26. 4 +.IP 29. 4 \fBReplace tag\fR 'old tag' with 'new tag': .PP .EX @@ -575,7 +607,7 @@ Show details of the \fBlast 10 bookmarks\fR: .B buku --replace 'old tag' 'new tag' .EE .PP -.IP 27. 4 +.IP 30. 4 \fBDelete tag\fR 'old tag' from DB: .PP .EX @@ -583,7 +615,7 @@ Show details of the \fBlast 10 bookmarks\fR: .B buku --replace 'old tag' .EE .PP -.IP 28. 4 +.IP 31. 4 \fBAppend (or delete) tags\fR 'tag 1', 'tag 2' to (or from) existing tags of bookmark at index 15012014: .PP .EX @@ -592,7 +624,7 @@ Show details of the \fBlast 10 bookmarks\fR: .B buku -u 15012014 --tag - tag 1, tag 2 .EE .PP -.IP 29. 4 +.IP 32. 4 \fBOpen URL\fR at index 15012014 in browser: .PP .EX @@ -600,7 +632,7 @@ Show details of the \fBlast 10 bookmarks\fR: .B buku -o 15012014 .EE .PP -.IP 30. 4 +.IP 33. 4 List bookmarks with \fBno title or tags\fR for bookkeeping: .PP .EX @@ -608,7 +640,7 @@ List bookmarks with \fBno title or tags\fR for bookkeeping: .B buku -S blank .EE .PP -.IP 31. 4 +.IP 34. 4 List bookmarks with \fBimmutable title\fR: .PP .EX @@ -616,7 +648,7 @@ List bookmarks with \fBimmutable title\fR: .B buku -S immutable .EE .PP -.IP 32. 4 +.IP 35. 4 \fBShorten\fR the URL www.google.com and the URL at index 20: .PP .EX @@ -625,7 +657,7 @@ List bookmarks with \fBimmutable title\fR: .B buku --shorten 20 .EE .PP -.IP 33. 4 +.IP 36. 4 \fBAppend, remove tags at prompt\fR (taglist index to the left, bookmark index to the right): .PP .EX diff --git a/buku.py b/buku.py index 6cbeb1b..5440be7 100755 --- a/buku.py +++ b/buku.py @@ -1079,18 +1079,32 @@ class BukuDb: return self.cur.fetchall() - def search_by_tag(self, tag): + def search_by_tag(self, tags): '''Search and list bookmarks with a tag - :param tag: a tag to search as string + :param tags: list of tags to search as string :return: search results, or None, if no matches ''' - tag = delim_wrap(tag.strip(DELIM)) - query = "SELECT id, url, metadata, tags, desc FROM bookmarks WHERE tags LIKE '%' || ? || '%' ORDER BY id ASC" - logdbg('query: "%s", args: %s', query, tag) + # do not allow combination of search logics + if ' + ' in tags and ',' in tags: + logerr("Cannot use both '+' and ',' in same search") + return - self.cur.execute(query, (tag,)) + tags, search_operator, excluded_tags = prep_tag_search(tags) + + query = "SELECT id, url, metadata, tags, desc FROM bookmarks WHERE tags LIKE '%' || ? || '%' " + for tag in tags[1:]: + query += "{} tags LIKE '%' || ? || '%' ".format(search_operator) + if excluded_tags: + tags.append(excluded_tags) + query = query.replace('WHERE tags', 'WHERE (tags') + query += ') AND tags NOT REGEXP ? ' + query += 'ORDER BY id ASC' + + logdbg('query: "%s", args: %s', query, tags) + + self.cur.execute(query, tuple(tags, )) return self.cur.fetchall() def compactdb(self, index, delay_commit=False): @@ -2042,7 +2056,7 @@ PROMPT KEYS: S keyword [...] search for records with ALL keywords d match substrings ('pen' matches 'opened') r expression run a regex search - t [...] search bookmarks by a tag or show taglist + t [...] search bookmarks by tags or show taglist list index after a tag listing shows records with the tag o id|range [...] browse bookmarks by indices and/or ranges p id|range [...] print bookmarks by indices and/or ranges @@ -2326,6 +2340,37 @@ def parse_tags(keywords=[]): return delim_wrap(DELIM.join(sorted_tags)) +def prep_tag_search(tags): + """Prepare list of tags to search and determine search operator + + :param tags: list of tags to search as string + :return: tuple ( + list of formatted tags to search, + a string indicating query search operator (either OR or AND), + a regex string of tags (used to exclude matching bookmarks), + ), + None, if ' - ' operator is not in the tags argument + """ + + excluded_tags = None + if ' - ' in tags: + tags, excluded_tags = tags.split(' - ', 1) + + excluded_taglist = [delim_wrap(t.strip()) for t in excluded_tags.split(',')] + # join with pipe to construct regex string + excluded_tags = '|'.join(excluded_taglist) + + search_operator = 'OR' + tag_delim = ',' + if ' + ' in tags: + search_operator = 'AND' + tag_delim = ' + ' + + tags = [delim_wrap(t.strip()) for t in tags.split(tag_delim)] + + return tags, search_operator, excluded_tags + + def edit_at_prompt(obj, nav): '''Edit and add or update a bookmark @@ -3101,7 +3146,11 @@ POSITIONAL ARGUMENTS: "immutable": entries with locked title --deep match substrings ('pen' matches 'opens') -r, --sreg run a regex search - -t, --stag search bookmarks by a tag + -t, --stag [tag [,|+] ...] [- tag, ...] + search bookmarks by tags + use ',' to find entries matching ANY tag + use '+' to find entries matching ALL tags + excludes entries matching tags following ' - ' list all tags, if no search keywords''') addarg = search_grp.add_argument addarg('-s', '--sany', action='store_true', help=HIDE) diff --git a/tests/test_buku.py b/tests/test_buku.py index ed6a213..fc83b1d 100644 --- a/tests/test_buku.py +++ b/tests/test_buku.py @@ -8,7 +8,7 @@ import unittest import pytest -from buku import is_int, parse_tags +from buku import is_int, parse_tags, prep_tag_search only_python_3_5 = pytest.mark.skipif( sys.version_info < (3, 5), reason="requires Python 3.5 or later") @@ -107,6 +107,30 @@ def test_parse_tags(keywords, exp_res): assert res == exp_res +@pytest.mark.parametrize( + 'taglist, exp_res', + [ + [ + 'tag1, tag2+3', + ([',tag1,', ',tag2+3,'], 'OR', None) + ], + [ + 'tag1 + tag2-3 + tag4', + ([',tag1,', ',tag2-3,', ',tag4,'], 'AND', None) + ], + [ + 'tag1, tag2-3 - tag4, tag5', + ([',tag1,', ',tag2-3,'], 'OR', ',tag4,|,tag5,') + ] + ] +) +def test_prep_tag_search(taglist, exp_res): + """test prep_tag_search helper function""" + + results = prep_tag_search(taglist) + assert results == exp_res + + @pytest.mark.parametrize( 'nav, is_editor_valid_retval, edit_rec_retval', product( diff --git a/tests/test_bukuDb.py b/tests/test_bukuDb.py index cb132af..5415072 100644 --- a/tests/test_bukuDb.py +++ b/tests/test_bukuDb.py @@ -273,6 +273,169 @@ class TestBukuDb(unittest.TestCase): expected = [(i + 1,) + tuple(self.bookmarks[i])] self.assertEqual(results, expected) + def test_search_by_multiple_tags_search_any(self): + # adding bookmarks + for bookmark in self.bookmarks: + self.bdb.add_rec(*bookmark) + + new_bookmark = ['https://newbookmark.com', + 'New Bookmark', + parse_tags(['test,old,new']), + 'additional bookmark to test multiple tag search'] + + self.bdb.add_rec(*new_bookmark) + + with mock.patch('buku.prompt'): + # search for bookmarks matching ANY of the supplied tags + results = self.bdb.search_by_tag('test, old') + # Expect a list of five-element tuples containing all bookmark data + # db index, URL, title, tags, description + expected = [ + (1, 'http://slashdot.org', 'SLASHDOT', + parse_tags([',news,old,']), + "News for old nerds, stuff that doesn't matter"), + (3, 'https://test.com:8080', 'test', + parse_tags([',test,tes,est,es,']), + "a case for replace_tag test"), + (4, 'https://newbookmark.com', 'New Bookmark', + parse_tags([',test,old,new,']), + 'additional bookmark to test multiple tag search') + ] + self.assertEqual(results, expected) + + def test_search_by_multiple_tags_search_all(self): + # adding bookmarks + for bookmark in self.bookmarks: + self.bdb.add_rec(*bookmark) + + new_bookmark = ['https://newbookmark.com', + 'New Bookmark', + parse_tags(['test,old,new']), + 'additional bookmark to test multiple tag search'] + + self.bdb.add_rec(*new_bookmark) + + with mock.patch('buku.prompt'): + # search for bookmarks matching ALL of the supplied tags + results = self.bdb.search_by_tag('test + old') + # Expect a list of five-element tuples containing all bookmark data + # db index, URL, title, tags, description + expected = [ + (4, 'https://newbookmark.com', 'New Bookmark', + parse_tags([',test,old,new,']), + 'additional bookmark to test multiple tag search') + ] + self.assertEqual(results, expected) + + def test_search_by_tags_enforces_space_seprations_search_all(self): + + bookmark1 = ['https://bookmark1.com', + 'Bookmark One', + parse_tags(['tag, two,tag+two']), + "test case for bookmark with '+' in tag"] + + bookmark2 = ['https://bookmark2.com', + 'Bookmark Two', + parse_tags(['tag,two, tag-two']), + "test case for bookmark with hyphenated tag"] + + self.bdb.add_rec(*bookmark1) + self.bdb.add_rec(*bookmark2) + + with mock.patch('buku.prompt'): + # check that space separation for ' + ' operator is enforced + results = self.bdb.search_by_tag('tag+two') + # Expect a list of five-element tuples containing all bookmark data + # db index, URL, title, tags, description + expected = [ + (1, 'https://bookmark1.com', 'Bookmark One', + parse_tags([',tag,two,tag+two,']), + "test case for bookmark with '+' in tag") + ] + self.assertEqual(results, expected) + results = self.bdb.search_by_tag('tag + two') + # Expect a list of five-element tuples containing all bookmark data + # db index, URL, title, tags, description + expected = [ + (1, 'https://bookmark1.com', 'Bookmark One', + parse_tags([',tag,two,tag+two,']), + "test case for bookmark with '+' in tag"), + (2, 'https://bookmark2.com', 'Bookmark Two', + parse_tags([',tag,two,tag-two,']), + "test case for bookmark with hyphenated tag"), + ] + self.assertEqual(results, expected) + + def test_search_by_tags_exclusion(self): + # adding bookmarks + for bookmark in self.bookmarks: + self.bdb.add_rec(*bookmark) + + new_bookmark = ['https://newbookmark.com', + 'New Bookmark', + parse_tags(['test,old,new']), + 'additional bookmark to test multiple tag search'] + + self.bdb.add_rec(*new_bookmark) + + with mock.patch('buku.prompt'): + # search for bookmarks matching ANY of the supplied tags + # while excluding bookmarks from results that match a given tag + results = self.bdb.search_by_tag('test, old - est') + # Expect a list of five-element tuples containing all bookmark data + # db index, URL, title, tags, description + expected = [ + (1, 'http://slashdot.org', 'SLASHDOT', + parse_tags([',news,old,']), + "News for old nerds, stuff that doesn't matter"), + (4, 'https://newbookmark.com', 'New Bookmark', + parse_tags([',test,old,new,']), + 'additional bookmark to test multiple tag search') + ] + self.assertEqual(results, expected) + + def test_search_by_tags_enforces_space_seprations_exclusion(self): + + bookmark1 = ['https://bookmark1.com', + 'Bookmark One', + parse_tags(['tag, two,tag+two']), + "test case for bookmark with '+' in tag"] + + bookmark2 = ['https://bookmark2.com', + 'Bookmark Two', + parse_tags(['tag,two, tag-two']), + "test case for bookmark with hyphenated tag"] + + bookmark3 = ['https://bookmark3.com', + 'Bookmark Three', + parse_tags(['tag, tag three']), + "second test case for bookmark with hyphenated tag"] + + self.bdb.add_rec(*bookmark1) + self.bdb.add_rec(*bookmark2) + self.bdb.add_rec(*bookmark3) + + with mock.patch('buku.prompt'): + # check that space separation for ' - ' operator is enforced + results = self.bdb.search_by_tag('tag-two') + # Expect a list of five-element tuples containing all bookmark data + # db index, URL, title, tags, description + expected = [ + (2, 'https://bookmark2.com', 'Bookmark Two', + parse_tags([',tag,two,tag-two,']), + "test case for bookmark with hyphenated tag"), + ] + self.assertEqual(results, expected) + results = self.bdb.search_by_tag('tag - two') + # Expect a list of five-element tuples containing all bookmark data + # db index, URL, title, tags, description + expected = [ + (3, 'https://bookmark3.com', 'Bookmark Three', + parse_tags([',tag,tag three,']), + "second test case for bookmark with hyphenated tag"), + ] + self.assertEqual(results, expected) + # @unittest.skip('skipping') def test_search_and_open_in_broswer_by_range(self): # adding bookmarks @@ -427,7 +590,6 @@ class TestBukuDb(unittest.TestCase): # def test_import_bookmark(self): # self.fail() - @given( index=st.integers(min_value=-10, max_value=10), low=st.integers(min_value=-10, max_value=10), @@ -824,6 +986,45 @@ def test_update_rec_exec_arg(caplog, kwargs, exp_query, exp_arguments): assert caplog.records[-1].levelname == 'DEBUG' +@pytest.mark.parametrize( + 'tags_to_search, exp_query, exp_arguments', + [ + [ + 'tag1, tag2', + "SELECT id, url, metadata, tags, desc FROM bookmarks WHERE tags LIKE '%' || ? || '%' " + "OR tags LIKE '%' || ? || '%' ORDER BY id ASC", + [',tag1,', ',tag2,'] + + ], + [ + 'tag1+tag2,tag3, tag4', + "SELECT id, url, metadata, tags, desc FROM bookmarks WHERE tags LIKE '%' || ? || '%' " + "OR tags LIKE '%' || ? || '%' OR tags LIKE '%' || ? || '%' ORDER BY id ASC", + [',tag1+tag2,', ',tag3,', ',tag4,'] + ], + [ + 'tag1 + tag2+tag3', + "SELECT id, url, metadata, tags, desc FROM bookmarks WHERE tags LIKE '%' || ? || '%' " + "AND tags LIKE '%' || ? || '%' ORDER BY id ASC", + [',tag1,', ',tag2+tag3,'] + ], + [ + 'tag1-tag2 + tag 3 - tag4', + "SELECT id, url, metadata, tags, desc FROM bookmarks WHERE (tags LIKE '%' || ? || '%' " + "AND tags LIKE '%' || ? || '%' ) AND tags NOT REGEXP ? ORDER BY id ASC", + [',tag1-tag2,', ',tag 3,', ',tag4,'] + ] + ] +) +def test_search_by_tag_query(caplog, tags_to_search, exp_query, exp_arguments): + """test that the correct query and argments are constructed""" + bdb = BukuDb() + bdb.search_by_tag(tags_to_search) + exp_log = 'query: "{}", args: {}'.format(exp_query, exp_arguments) + assert caplog.records[-1].getMessage() == exp_log + assert caplog.records[-1].levelname == 'DEBUG' + + def test_update_rec_only_index(): """test method.""" bdb = BukuDb()