buku/tests/test_bukuDb.py
2021-05-01 23:17:24 +08:00

1494 lines
49 KiB
Python

#!/usr/bin/env python3
#
# Unit test cases for buku
#
import logging
import math
import os
import re
import shutil
import sqlite3
import sys
import unittest
import urllib
import zipfile
# fmt: off
from tempfile import NamedTemporaryFile, TemporaryDirectory
from unittest import mock
from genericpath import exists
# fmt: on
import pytest
import vcr
import yaml
from hypothesis import example, given, settings
from hypothesis import strategies as st
from buku import BukuDb, parse_tags, prompt
logging.basicConfig() # you need to initialize logging, otherwise you will not see anything from vcrpy
vcr_log = logging.getLogger("vcr")
vcr_log.setLevel(logging.INFO)
def get_temp_dir_path():
with TemporaryDirectory(prefix="bukutest_") as dir_obj:
return dir_obj
TEST_TEMP_DIR_PATH = get_temp_dir_path()
TEST_TEMP_DBDIR_PATH = os.path.join(TEST_TEMP_DIR_PATH, "buku")
TEST_TEMP_DBFILE_PATH = os.path.join(TEST_TEMP_DBDIR_PATH, "bookmarks.db")
MAX_SQLITE_INT = int(math.pow(2, 63) - 1)
TEST_PRINT_REC = ("https://example.com", "", parse_tags(["cat,ant,bee,1"]), "")
TEST_BOOKMARKS = [
[
"http://slashdot.org",
"SLASHDOT",
parse_tags(["old,news"]),
"News for old nerds, stuff that doesn't matter",
],
[
"http://www.zażółćgęśląjaźń.pl/",
"ZAŻÓŁĆ",
parse_tags(["zażółć,gęślą,jaźń"]),
"Testing UTF-8, zażółć gęślą jaźń.",
],
[
"http://example.com/",
"test",
parse_tags(["test,tes,est,es"]),
"a case for replace_tag test",
],
]
only_python_3_5 = pytest.mark.skipif(
sys.version_info < (3, 5), reason="requires Python 3.5 or later"
)
@pytest.fixture(scope="module")
def vcr_cassette_dir(request):
# Put all cassettes in vhs/{module}/{test}.yaml
return os.path.join("tests", "vcr_cassettes", request.module.__name__)
@pytest.fixture()
def setup():
os.environ["XDG_DATA_HOME"] = TEST_TEMP_DIR_PATH
# start every test from a clean state
if exists(TEST_TEMP_DBFILE_PATH):
os.remove(TEST_TEMP_DBFILE_PATH)
class PrettySafeLoader(
yaml.SafeLoader
): # pylint: disable=too-many-ancestors,too-few-public-methods
def construct_python_tuple(self, node):
return tuple(self.construct_sequence(node))
PrettySafeLoader.add_constructor(
u"tag:yaml.org,2002:python/tuple", PrettySafeLoader.construct_python_tuple
)
class TestBukuDb(unittest.TestCase):
def setUp(self):
os.environ["XDG_DATA_HOME"] = TEST_TEMP_DIR_PATH
# start every test from a clean state
if exists(TEST_TEMP_DBFILE_PATH):
os.remove(TEST_TEMP_DBFILE_PATH)
self.bookmarks = TEST_BOOKMARKS
self.bdb = BukuDb()
def tearDown(self):
os.environ["XDG_DATA_HOME"] = TEST_TEMP_DIR_PATH
@pytest.mark.non_tox
def test_get_default_dbdir(self):
dbdir_expected = TEST_TEMP_DBDIR_PATH
dbdir_local_expected = os.path.join(
os.path.expanduser("~"), ".local", "share", "buku"
)
dbdir_relative_expected = os.path.abspath(".")
# desktop linux
self.assertEqual(dbdir_expected, BukuDb.get_default_dbdir())
# desktop generic
os.environ.pop("XDG_DATA_HOME")
self.assertEqual(dbdir_local_expected, BukuDb.get_default_dbdir())
# no desktop
# -- home is defined differently on various platforms.
# -- keep a copy and set it back once done
originals = {}
for env_var in ["HOME", "HOMEPATH", "HOMEDIR"]:
try:
originals[env_var] = os.environ.pop(env_var)
except KeyError:
pass
self.assertEqual(dbdir_relative_expected, BukuDb.get_default_dbdir())
for key, value in list(originals.items()):
os.environ[key] = value
# # not sure how to test this in nondestructive manner
# def test_move_legacy_dbfile(self):
# self.fail()
def test_initdb(self):
if exists(TEST_TEMP_DBFILE_PATH):
os.remove(TEST_TEMP_DBFILE_PATH)
self.assertIs(False, exists(TEST_TEMP_DBFILE_PATH))
conn, curr = BukuDb.initdb()
self.assertIsInstance(conn, sqlite3.Connection)
self.assertIsInstance(curr, sqlite3.Cursor)
self.assertIs(True, exists(TEST_TEMP_DBFILE_PATH))
curr.close()
conn.close()
def test_get_rec_by_id(self):
for bookmark in self.bookmarks:
# adding bookmark from self.bookmarks
self.bdb.add_rec(*bookmark)
# the expected bookmark
expected = (
1,
"http://slashdot.org",
"SLASHDOT",
",news,old,",
"News for old nerds, stuff that doesn't matter",
0,
)
bookmark_from_db = self.bdb.get_rec_by_id(1)
# asserting bookmark matches expected
self.assertEqual(expected, bookmark_from_db)
# asserting None returned if index out of range
self.assertIsNone(self.bdb.get_rec_by_id(len(self.bookmarks[0]) + 1))
def test_get_rec_id(self):
for idx, bookmark in enumerate(self.bookmarks):
# adding bookmark from self.bookmarks to database
self.bdb.add_rec(*bookmark)
# asserting index is in order
idx_from_db = self.bdb.get_rec_id(bookmark[0])
self.assertEqual(idx + 1, idx_from_db)
# asserting -1 is returned for nonexistent url
idx_from_db = self.bdb.get_rec_id("http://nonexistent.url")
self.assertEqual(-1, idx_from_db)
def test_add_rec(self):
for bookmark in self.bookmarks:
# adding bookmark from self.bookmarks to database
self.bdb.add_rec(*bookmark)
# retrieving bookmark from database
index = self.bdb.get_rec_id(bookmark[0])
from_db = self.bdb.get_rec_by_id(index)
self.assertIsNotNone(from_db)
# comparing data
for pair in zip(from_db[1:], bookmark):
self.assertEqual(*pair)
# TODO: tags should be passed to the api as a sequence...
def test_suggest_tags(self):
for bookmark in self.bookmarks:
self.bdb.add_rec(*bookmark)
tagstr = ",test,old,"
with mock.patch("builtins.input", return_value="1 2 3"):
expected_results = ",es,est,news,old,test,"
suggested_results = self.bdb.suggest_similar_tag(tagstr)
self.assertEqual(expected_results, suggested_results)
# returns user supplied tags if none are in the DB
tagstr = ",uniquetag1,uniquetag2,"
expected_results = tagstr
suggested_results = self.bdb.suggest_similar_tag(tagstr)
self.assertEqual(expected_results, suggested_results)
def test_update_rec(self):
old_values = self.bookmarks[0]
new_values = self.bookmarks[1]
# adding bookmark and getting index
self.bdb.add_rec(*old_values)
index = self.bdb.get_rec_id(old_values[0])
# updating with new values
self.bdb.update_rec(index, *new_values)
# retrieving bookmark from database
from_db = self.bdb.get_rec_by_id(index)
self.assertIsNotNone(from_db)
# checking if values are updated
for pair in zip(from_db[1:], new_values):
self.assertEqual(*pair)
def test_append_tag_at_index(self):
for bookmark in self.bookmarks:
self.bdb.add_rec(*bookmark)
# tags to add
old_tags = self.bdb.get_rec_by_id(1)[3]
new_tags = ",foo,bar,baz"
self.bdb.append_tag_at_index(1, new_tags)
# updated list of tags
from_db = self.bdb.get_rec_by_id(1)[3]
# checking if new tags were added to the bookmark
self.assertTrue(split_and_test_membership(new_tags, from_db))
# checking if old tags still exist
self.assertTrue(split_and_test_membership(old_tags, from_db))
def test_append_tag_at_all_indices(self):
for bookmark in self.bookmarks:
self.bdb.add_rec(*bookmark)
# tags to add
new_tags = ",foo,bar,baz"
# record of original tags for each bookmark
old_tagsets = {
i: self.bdb.get_rec_by_id(i)[3]
for i in inclusive_range(1, len(self.bookmarks))
}
with mock.patch("builtins.input", return_value="y"):
self.bdb.append_tag_at_index(0, new_tags)
# updated tags for each bookmark
from_db = [
(i, self.bdb.get_rec_by_id(i)[3])
for i in inclusive_range(1, len(self.bookmarks))
]
for index, tagset in from_db:
# checking if new tags added to bookmark
self.assertTrue(split_and_test_membership(new_tags, tagset))
# checking if old tags still exist for boomark
self.assertTrue(split_and_test_membership(old_tagsets[index], tagset))
def test_delete_tag_at_index(self):
# adding bookmarks
for bookmark in self.bookmarks:
self.bdb.add_rec(*bookmark)
get_tags_at_idx = lambda i: self.bdb.get_rec_by_id(i)[3]
# list of two-tuples, each containg bookmark index and corresponding tags
tags_by_index = [
(i, get_tags_at_idx(i)) for i in inclusive_range(1, len(self.bookmarks))
]
for i, tags in tags_by_index:
# get the first tag from the bookmark
to_delete = re.match(",.*?,", tags).group(0)
self.bdb.delete_tag_at_index(i, to_delete)
# get updated tags from db
from_db = get_tags_at_idx(i)
self.assertNotIn(to_delete, from_db)
def test_search_keywords_and_filter_by_tags(self):
# adding bookmark
for bookmark in self.bookmarks:
self.bdb.add_rec(*bookmark)
with mock.patch("buku.prompt"):
expected = [
(
3,
"http://example.com/",
"test",
",es,est,tes,test,",
"a case for replace_tag test",
0,
)
]
results = self.bdb.search_keywords_and_filter_by_tags(
["News", "case"],
False,
False,
False,
["est"],
)
self.assertIn(expected[0], results)
expected = [
(
3,
"http://example.com/",
"test",
",es,est,tes,test,",
"a case for replace_tag test",
0,
),
(
2,
"http://www.zażółćgęśląjaźń.pl/",
"ZAŻÓŁĆ",
",gęślą,jaźń,zażółć,",
"Testing UTF-8, zażółć gęślą jaźń.",
0,
),
]
results = self.bdb.search_keywords_and_filter_by_tags(
["UTF-8", "case"],
False,
False,
False,
"jaźń, test",
)
self.assertIn(expected[0], results)
self.assertIn(expected[1], results)
def test_searchdb(self):
# adding bookmarks
for bookmark in self.bookmarks:
self.bdb.add_rec(*bookmark)
get_first_tag = lambda x: "".join(x[2].split(",")[:2])
for i, bookmark in enumerate(self.bookmarks):
tag_search = get_first_tag(bookmark)
# search by the domain name for url
url_search = re.match(r"https?://(.*)?\..*", bookmark[0]).group(1)
title_search = bookmark[1]
# Expect a five-tuple containing all bookmark data
# db index, URL, title, tags, description
expected = [(i + 1,) + tuple(bookmark)]
expected[0] += tuple([0])
# search db by tag, url (domain name), and title
for keyword in (tag_search, url_search, title_search):
with mock.patch("buku.prompt"):
# search by keyword
results = self.bdb.searchdb([keyword])
self.assertEqual(results, expected)
def test_search_by_tag(self):
# adding bookmarks
for bookmark in self.bookmarks:
self.bdb.add_rec(*bookmark)
with mock.patch("buku.prompt"):
get_first_tag = lambda x: "".join(x[2].split(",")[:2])
for i in range(len(self.bookmarks)):
# search for bookmark with a tag that is known to exist
results = self.bdb.search_by_tag(get_first_tag(self.bookmarks[i]))
# Expect a five-tuple containing all bookmark data
# db index, URL, title, tags, description
expected = [(i + 1,) + tuple(self.bookmarks[i])]
expected[0] += tuple([0])
self.assertEqual(results, expected)
@vcr.use_cassette(
"tests/vcr_cassettes/test_search_by_multiple_tags_search_any.yaml"
)
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",
0,
]
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, ordered by records with
# the most number of matches.
expected = [
(
4,
"https://newbookmark.com",
"New Bookmark",
parse_tags([",test,old,new,"]),
"additional bookmark to test multiple tag search",
0,
),
(
1,
"http://slashdot.org",
"SLASHDOT",
parse_tags([",news,old,"]),
"News for old nerds, stuff that doesn't matter",
0,
),
(
3,
"http://example.com/",
"test",
",es,est,tes,test,",
"a case for replace_tag test",
0,
),
]
self.assertEqual(results, expected)
@vcr.use_cassette(
"tests/vcr_cassettes/test_search_by_multiple_tags_search_all.yaml"
)
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",
0,
)
]
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",
0,
)
]
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",
0,
),
(
2,
"https://bookmark2.com",
"Bookmark Two",
parse_tags([",tag,two,tag-two,"]),
"test case for bookmark with hyphenated tag",
0,
),
]
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 = [
(
4,
"https://newbookmark.com",
"New Bookmark",
parse_tags([",test,old,new,"]),
"additional bookmark to test multiple tag search",
0,
),
(
1,
"http://slashdot.org",
"SLASHDOT",
parse_tags([",news,old,"]),
"News for old nerds, stuff that doesn't matter",
0,
),
]
self.assertEqual(results, expected)
@vcr.use_cassette(
"tests/vcr_cassettes/test_search_by_tags_enforces_space_seprations_exclusion.yaml"
)
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",
0,
),
]
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",
0,
),
]
self.assertEqual(results, expected)
def test_search_and_open_in_broswer_by_range(self):
# adding bookmarks
for bookmark in self.bookmarks:
self.bdb.add_rec(*bookmark)
# simulate user input, select range of indices 1-3
index_range = "1-%s" % len(self.bookmarks)
with mock.patch("builtins.input", side_effect=[index_range]):
with mock.patch("buku.browse") as mock_browse:
try:
# search the db with keywords from each bookmark
# searching using the first tag from bookmarks
get_first_tag = lambda x: x[2].split(",")[1]
results = self.bdb.searchdb(
[get_first_tag(bm) for bm in self.bookmarks]
)
prompt(self.bdb, results)
except StopIteration:
# catch exception thrown by reaching the end of the side effect iterable
pass
# collect arguments passed to browse
arg_list = [args[0] for args, _ in mock_browse.call_args_list]
# expect a list of one-tuples that are bookmark URLs
expected = [x[0] for x in self.bookmarks]
# checking if browse called with expected arguments
self.assertEqual(arg_list, expected)
@vcr.use_cassette("tests/vcr_cassettes/test_search_and_open_all_in_browser.yaml")
def test_search_and_open_all_in_browser(self):
# adding bookmarks
for bookmark in self.bookmarks:
self.bdb.add_rec(*bookmark)
# simulate user input, select 'a' to open all bookmarks in results
with mock.patch("builtins.input", side_effect=["a"]):
with mock.patch("buku.browse") as mock_browse:
try:
# search the db with keywords from each bookmark
# searching using the first tag from bookmarks
get_first_tag = lambda x: x[2].split(",")[1]
results = self.bdb.searchdb(
[get_first_tag(bm) for bm in self.bookmarks[:2]]
)
prompt(self.bdb, results)
except StopIteration:
# catch exception thrown by reaching the end of the side effect iterable
pass
# collect arguments passed to browse
arg_list = [args[0] for args, _ in mock_browse.call_args_list]
# expect a list of one-tuples that are bookmark URLs
expected = [x[0] for x in self.bookmarks][:2]
# checking if browse called with expected arguments
self.assertEqual(arg_list, expected)
def test_delete_rec(self):
# adding bookmark and getting index
self.bdb.add_rec(*self.bookmarks[0])
index = self.bdb.get_rec_id(self.bookmarks[0][0])
# deleting bookmark
self.bdb.delete_rec(index)
# asserting it doesn't exist
from_db = self.bdb.get_rec_by_id(index)
self.assertIsNone(from_db)
def test_delete_rec_yes(self):
# checking that "y" response causes delete_rec to return True
with mock.patch("builtins.input", return_value="y"):
self.assertTrue(self.bdb.delete_rec(0))
def test_delete_rec_no(self):
# checking that non-"y" response causes delete_rec to return None
with mock.patch("builtins.input", return_value="n"):
self.assertFalse(self.bdb.delete_rec(0))
def test_cleardb(self):
# adding bookmarks
self.bdb.add_rec(*self.bookmarks[0])
# deleting all bookmarks
with mock.patch("builtins.input", return_value="y"):
self.bdb.cleardb()
# assert table has been dropped
assert self.bdb.get_rec_by_id(0) is None
def test_replace_tag(self):
indices = []
for bookmark in self.bookmarks:
# adding bookmark, getting index
self.bdb.add_rec(*bookmark)
index = self.bdb.get_rec_id(bookmark[0])
indices += [index]
# replacing tags
with mock.patch("builtins.input", return_value="y"):
self.bdb.replace_tag("news", ["__01"])
with mock.patch("builtins.input", return_value="y"):
self.bdb.replace_tag("zażółć", ["__02,__03"])
# replacing tag which is also a substring of other tag
with mock.patch("builtins.input", return_value="y"):
self.bdb.replace_tag("es", ["__04"])
# removing tags
with mock.patch("builtins.input", return_value="y"):
self.bdb.replace_tag("gęślą")
with mock.patch("builtins.input", return_value="y"):
self.bdb.replace_tag("old")
# removing non-existent tag
with mock.patch("builtins.input", return_value="y"):
self.bdb.replace_tag("_")
# removing nonexistent tag which is also a substring of other tag
with mock.patch("builtins.input", return_value="y"):
self.bdb.replace_tag("e")
for url, title, _, _ in self.bookmarks:
# retrieving from db
index = self.bdb.get_rec_id(url)
from_db = self.bdb.get_rec_by_id(index)
# asserting tags were replaced
if title == "SLASHDOT":
self.assertEqual(from_db[3], parse_tags(["__01"]))
elif title == "ZAŻÓŁĆ":
self.assertEqual(from_db[3], parse_tags(["__02,__03,jaźń"]))
elif title == "test":
self.assertEqual(from_db[3], parse_tags(["test,tes,est,__04"]))
def test_tnyfy_url(self):
# shorten a well-known url
shorturl = self.bdb.tnyfy_url(url="https://www.google.com", shorten=True)
self.assertEqual(shorturl, "http://tny.im/yt")
# expand a well-known short url
url = self.bdb.tnyfy_url(url="http://tny.im/yt", shorten=False)
self.assertEqual(url, "https://www.google.com")
# def test_browse_by_index(self):
# self.fail()
def test_close_quit(self):
# quitting with no args
try:
self.bdb.close_quit()
except SystemExit as err:
self.assertEqual(err.args[0], 0)
# quitting with custom arg
try:
self.bdb.close_quit(1)
except SystemExit as err:
self.assertEqual(err.args[0], 1)
# def test_import_bookmark(self):
# self.fail()
@pytest.fixture(scope="function")
def refreshdb_fixture():
# Setup
os.environ["XDG_DATA_HOME"] = TEST_TEMP_DIR_PATH
# start every test from a clean state
if exists(TEST_TEMP_DBFILE_PATH):
os.remove(TEST_TEMP_DBFILE_PATH)
bdb = BukuDb()
yield bdb
# Teardown
os.environ["XDG_DATA_HOME"] = TEST_TEMP_DIR_PATH
@pytest.mark.parametrize(
"title_in, exp_res",
[
["?", "Example Domain"],
[None, "Example Domain"],
["", "Example Domain"],
["random title", "Example Domain"],
],
)
def test_refreshdb(refreshdb_fixture, title_in, exp_res):
bdb = refreshdb_fixture
args = ["http://example.com"]
if title_in:
args.append(title_in)
bdb.add_rec(*args)
bdb.refreshdb(1, 1)
from_db = bdb.get_rec_by_id(1)
assert from_db[2] == exp_res, "from_db: {}".format(from_db)
@pytest.fixture
def test_print_caplog(caplog):
caplog.handler.records.clear()
caplog.records.clear()
yield caplog
@pytest.mark.parametrize(
"kwargs, rec, exp_res",
[
[{}, TEST_PRINT_REC, (True, [])],
[{"is_range": True}, TEST_PRINT_REC, (True, [])],
[{"index": 0}, TEST_PRINT_REC, (True, [])],
[{"index": -1}, TEST_PRINT_REC, (True, [])],
[{"index": -2}, TEST_PRINT_REC, (True, [])],
[{"index": 2}, TEST_PRINT_REC, (False, [("root", 40, "No matching index 2")])],
[{"low": -1, "high": -1}, TEST_PRINT_REC, (True, [])],
[
{"low": -1, "high": -1, "is_range": True},
TEST_PRINT_REC,
(False, [("root", 40, "Negative range boundary")]),
],
[{"low": 0, "high": 0, "is_range": True}, TEST_PRINT_REC, (True, [])],
[{"low": 0, "high": 1, "is_range": True}, TEST_PRINT_REC, (True, [])],
[{"low": 0, "high": 2, "is_range": True}, TEST_PRINT_REC, (True, [])],
[{"low": 2, "high": 2, "is_range": True}, TEST_PRINT_REC, (True, [])],
[{"low": 2, "high": 3, "is_range": True}, TEST_PRINT_REC, (True, [])],
# empty database
[{"is_range": True}, None, (True, [])],
[{"index": 0}, None, (True, [("root", 40, "0 records")])],
[{"index": -1}, None, (False, [("root", 40, "Empty database")])],
[{"index": 1}, None, (False, [("root", 40, "No matching index 1")])],
[{"low": -1, "high": -1}, TEST_PRINT_REC, (True, [])],
[
{"low": -1, "high": -1, "is_range": True},
None,
(False, [("root", 40, "Negative range boundary")]),
],
[{"low": 0, "high": 0, "is_range": True}, None, (True, [])],
[{"low": 0, "high": 1, "is_range": True}, None, (True, [])],
[{"low": 0, "high": 2, "is_range": True}, None, (True, [])],
[{"low": 2, "high": 2, "is_range": True}, None, (True, [])],
[{"low": 2, "high": 3, "is_range": True}, None, (True, [])],
],
)
def test_print_rec(setup, kwargs, rec, exp_res, tmp_path, caplog):
bdb = BukuDb(dbfile=tmp_path / "tmp.db")
if rec:
bdb.add_rec(*rec)
# run the function
assert (bdb.print_rec(**kwargs), caplog.record_tuples) == exp_res
def test_list_tags(capsys, setup):
bdb = BukuDb()
# adding bookmarks
bdb.add_rec("http://one.com", "", parse_tags(["cat,ant,bee,1"]), "")
bdb.add_rec("http://two.com", "", parse_tags(["Cat,Ant,bee,1"]), "")
bdb.add_rec("http://three.com", "", parse_tags(["Cat,Ant,3,Bee,2"]), "")
# listing tags, asserting output
out, err = capsys.readouterr()
prompt(bdb, None, True, listtags=True)
out, err = capsys.readouterr()
exp_out = " 1. 1 (2)\n 2. 2 (1)\n 3. 3 (1)\n 4. ant (3)\n 5. bee (3)\n 6. cat (3)\n\n"
assert out == exp_out
assert err == ""
def test_compactdb(setup):
bdb = BukuDb()
# adding bookmarks
for bookmark in TEST_BOOKMARKS:
bdb.add_rec(*bookmark)
# manually deleting 2nd index from db, calling compactdb
bdb.cur.execute("DELETE FROM bookmarks WHERE id = ?", (2,))
bdb.compactdb(2)
# asserting bookmarks have correct indices
assert bdb.get_rec_by_id(1) == (
1,
"http://slashdot.org",
"SLASHDOT",
",news,old,",
"News for old nerds, stuff that doesn't matter",
0,
)
assert bdb.get_rec_by_id(2) == (
2,
"http://example.com/",
"test",
",es,est,tes,test,",
"a case for replace_tag test",
0,
)
assert bdb.get_rec_by_id(3) is None
@pytest.mark.vcr()
@pytest.mark.parametrize(
"low, high, delay_commit, input_retval, exp_res",
[
# delay_commit, y input_retval
[0, 0, True, "y", (True, [])],
# delay_commit, non-y input_retval
[
0,
0,
True,
"x",
(
False,
[tuple([x] + y + [0]) for x, y in zip(range(1, 4), TEST_BOOKMARKS)],
),
],
# non delay_commit, y input_retval
[0, 0, False, "y", (True, [])],
# non delay_commit, non-y input_retval
[
0,
0,
False,
"x",
(
False,
[tuple([x] + y + [0]) for x, y in zip(range(1, 4), TEST_BOOKMARKS)],
),
],
],
)
def test_delete_rec_range_and_delay_commit(
setup, tmp_path, low, high, delay_commit, input_retval, exp_res
):
"""test delete rec, range and delay commit."""
bdb = BukuDb(dbfile=tmp_path / "tmp.db")
kwargs = {"is_range": True, "low": low, "high": high, "delay_commit": delay_commit}
kwargs["index"] = 0
# Fill bookmark
for bookmark in TEST_BOOKMARKS:
bdb.add_rec(*bookmark)
with mock.patch("builtins.input", return_value=input_retval):
res = bdb.delete_rec(**kwargs)
assert (res, bdb.get_rec_all()) == exp_res
# teardown
os.environ["XDG_DATA_HOME"] = TEST_TEMP_DIR_PATH
@pytest.mark.parametrize(
"index, delay_commit, input_retval",
[
[-1, False, False],
[0, False, False],
[1, False, True],
[1, False, False],
[1, True, True],
[1, True, False],
[100, False, True],
],
)
def test_delete_rec_index_and_delay_commit(index, delay_commit, input_retval):
"""test delete rec, index and delay commit."""
bdb = BukuDb()
bdb_dc = BukuDb() # instance for delay_commit check.
# Fill bookmark
for bookmark in TEST_BOOKMARKS:
bdb.add_rec(*bookmark)
db_len = len(TEST_BOOKMARKS)
n_index = index
with mock.patch("builtins.input", return_value=input_retval):
res = bdb.delete_rec(index=index, delay_commit=delay_commit)
if n_index < 0:
assert not res
elif n_index > db_len:
assert not res
assert len(bdb.get_rec_all()) == db_len
elif index == 0 and input_retval != "y":
assert not res
assert len(bdb.get_rec_all()) == db_len
else:
assert res
assert len(bdb.get_rec_all()) == db_len - 1
if delay_commit:
assert len(bdb_dc.get_rec_all()) == db_len
else:
assert len(bdb_dc.get_rec_all()) == db_len - 1
# teardown
os.environ["XDG_DATA_HOME"] = TEST_TEMP_DIR_PATH
@pytest.mark.parametrize(
"index, is_range, low, high",
[
# range on non zero index
(0, True, 1, 1),
# range on zero index
(0, True, 0, 0),
# zero index only
(0, False, 0, 0),
],
)
def test_delete_rec_on_empty_database(setup, index, is_range, low, high):
"""test delete rec, on empty database."""
bdb = BukuDb()
with mock.patch("builtins.input", return_value="y"):
res = bdb.delete_rec(index, is_range, low, high)
if (is_range and any([low == 0, high == 0])) or (not is_range and index == 0):
assert res
# teardown
os.environ["XDG_DATA_HOME"] = TEST_TEMP_DIR_PATH
return
if is_range and low > 1 and high > 1:
assert not res
# teardown
os.environ["XDG_DATA_HOME"] = TEST_TEMP_DIR_PATH
@pytest.mark.parametrize(
"kwargs, exp_res, raise_error",
[
[dict(index="a", low="a", high=1, is_range=True), None, True],
[dict(index="a", low="a", high=1, is_range=False), None, True],
[dict(index="a", low=1, high="a", is_range=True), None, True],
[dict(index="a", is_range=False), None, True],
[dict(index="a", is_range=True), None, True],
],
)
def test_delete_rec_on_non_integer(
setup, tmp_path, monkeypatch, kwargs, exp_res, raise_error
):
"""test delete rec on non integer arg."""
import buku
bdb = BukuDb(dbfile=tmp_path / "tmp.db")
for bookmark in TEST_BOOKMARKS:
bdb.add_rec(*bookmark)
def mockreturn():
return "y"
exp_res = None
res = None
monkeypatch.setattr(buku, "read_in", mockreturn)
if raise_error:
with pytest.raises(TypeError):
res = bdb.delete_rec(**kwargs)
else:
res = bdb.delete_rec(**kwargs)
assert res == exp_res
@pytest.mark.parametrize("url", ["", False, None, 0])
def test_add_rec_add_invalid_url(caplog, url):
"""test method."""
bdb = BukuDb()
res = bdb.add_rec(url=url)
assert res == -1
caplog.records[0].levelname == "ERROR"
caplog.records[0].getMessage() == "Invalid URL"
@pytest.mark.parametrize(
"kwargs, exp_arg",
[
[{"url": "example.com"}, ("example.com", "Example Domain", ",", "", 0)],
[
{"url": "http://example.com"},
("http://example.com", "Example Domain", ",", "", 0),
],
[
{"url": "http://example.com", "immutable": 1},
("http://example.com", "Example Domain", ",", "", 1),
],
[
{"url": "http://example.com", "desc": "randomdesc"},
("http://example.com", "Example Domain", ",", "randomdesc", 0),
],
[
{"url": "http://example.com", "title_in": "randomtitle"},
("http://example.com", "randomtitle", ",", "", 0),
],
[
{"url": "http://example.com", "tags_in": "tag1"},
("http://example.com", "Example Domain", ",tag1,", "", 0),
],
[
{"url": "http://example.com", "tags_in": ",tag1"},
("http://example.com", "Example Domain", ",tag1,", "", 0),
],
[
{"url": "http://example.com", "tags_in": ",tag1,"},
("http://example.com", "Example Domain", ",tag1,", "", 0),
],
],
)
def test_add_rec_exec_arg(kwargs, exp_arg):
"""test func."""
bdb = BukuDb()
bdb.cur = mock.Mock()
bdb.get_rec_id = mock.Mock(return_value=-1)
bdb.add_rec(**kwargs)
assert bdb.cur.execute.call_args[0][1] == exp_arg
def test_update_rec_index_0(caplog):
"""test method."""
bdb = BukuDb()
res = bdb.update_rec(index=0, url="http://example.com")
assert not res
assert caplog.records[0].getMessage() == "All URLs cannot be same"
assert caplog.records[0].levelname == "ERROR"
@pytest.mark.parametrize(
"kwargs, exp_res",
[
[dict(index=1), False],
[dict(index=1, url="url"), False],
[dict(index=1, url=""), False],
],
)
def test_update_rec(tmp_path, kwargs, exp_res):
bdb = BukuDb(tmp_path / "tmp.db")
res = bdb.update_rec(**kwargs)
assert res == exp_res
@pytest.mark.parametrize("invalid_tag", ["+,", "-,"])
def test_update_rec_invalid_tag(caplog, invalid_tag):
"""test method."""
url = "http://example.com"
bdb = BukuDb()
res = bdb.update_rec(index=1, url=url, tags_in=invalid_tag)
assert not res
try:
assert caplog.records[0].getMessage() == "Please specify a tag"
assert caplog.records[0].levelname == "ERROR"
except IndexError as e:
if (sys.version_info.major, sys.version_info.minor) == (3, 4):
print("caplog records: {}".format(caplog.records))
for idx, record in enumerate(caplog.records):
print(
"idx:{};{};message:{};levelname:{}".format(
idx, record, record.getMessage(), record.levelname
)
)
else:
raise e
@pytest.mark.parametrize(
"read_in_retval, exp_res, record_tuples",
[
["y", False, [("root", 40, "No matching index 0")]],
["n", False, []],
["", False, []],
],
)
def test_update_rec_update_all_bookmark(
caplog, tmp_path, setup, read_in_retval, exp_res, record_tuples
):
"""test method."""
with mock.patch("buku.read_in", return_value=read_in_retval):
bdb = BukuDb(tmp_path / "tmp.db")
res = bdb.update_rec(index=0, tags_in="tags1")
assert (res, caplog.record_tuples) == (exp_res, record_tuples)
@pytest.mark.parametrize(
"get_system_editor_retval, index, exp_res",
[
["none", 0, False],
["nano", -2, False],
],
)
def test_edit_update_rec_with_invalid_input(get_system_editor_retval, index, exp_res):
"""test method."""
with mock.patch("buku.get_system_editor", return_value=get_system_editor_retval):
import buku
bdb = buku.BukuDb()
res = bdb.edit_update_rec(index=index)
assert res == exp_res
@vcr.use_cassette("tests/vcr_cassettes/test_browse_by_index.yaml")
@given(
low=st.integers(min_value=-2, max_value=3),
high=st.integers(min_value=-2, max_value=3),
index=st.integers(min_value=-2, max_value=3),
is_range=st.booleans(),
empty_database=st.booleans(),
)
@example(low=0, high=0, index=0, is_range=False, empty_database=True)
@settings(max_examples=2, deadline=None)
def test_browse_by_index(low, high, index, is_range, empty_database):
"""test method."""
n_low, n_high = (high, low) if low > high else (low, high)
with mock.patch("buku.browse"):
import buku
bdb = buku.BukuDb()
bdb.delete_rec_all()
db_len = 0
if not empty_database:
bdb.add_rec("https://www.google.com/ncr", "?")
db_len += 1
res = bdb.browse_by_index(index=index, low=low, high=high, is_range=is_range)
if is_range and (low < 0 or high < 0):
assert not res
elif is_range and n_low > 0 and n_high > 0:
assert res
elif is_range:
assert not res
elif not is_range and index < 0:
assert not res
elif not is_range and index > db_len:
assert not res
elif not is_range and index >= 0 and empty_database:
assert not res
elif not is_range and 0 <= index <= db_len and not empty_database:
assert res
else:
raise ValueError
bdb.delete_rec_all()
@pytest.fixture()
def chrome_db():
# compatibility
dir_path = os.path.dirname(os.path.realpath(__file__))
res_yaml_file = os.path.join(dir_path, "test_bukuDb", "25491522_res.yaml")
res_nopt_yaml_file = os.path.join(dir_path, "test_bukuDb", "25491522_res_nopt.yaml")
json_file = os.path.join(dir_path, "test_bukuDb", "Bookmarks")
return json_file, res_yaml_file, res_nopt_yaml_file
@pytest.mark.parametrize("add_pt", [True, False])
def test_load_chrome_database(chrome_db, add_pt):
"""test method."""
# compatibility
json_file = chrome_db[0]
res_yaml_file = chrome_db[1] if add_pt else chrome_db[2]
dump_data = False # NOTE: change this value to dump data
if not dump_data:
with open(res_yaml_file, "r") as f:
try:
res_yaml = yaml.load(f, Loader=yaml.FullLoader)
except RuntimeError:
res_yaml = yaml.load(f, Loader=PrettySafeLoader)
# init
import buku
bdb = buku.BukuDb()
bdb.add_rec = mock.Mock()
bdb.load_chrome_database(json_file, None, add_pt)
call_args_list_dict = dict(bdb.add_rec.call_args_list)
# test
if not dump_data:
assert call_args_list_dict == res_yaml
# dump data for new test
if dump_data:
with open(res_yaml_file, "w") as f:
yaml.dump(call_args_list_dict, f)
print("call args list dict dumped to:{}".format(res_yaml_file))
@pytest.fixture()
def firefox_db(tmpdir):
zip_url = "https://github.com/jarun/buku/files/1319933/bookmarks.zip"
dir_path = os.path.dirname(os.path.realpath(__file__))
res_yaml_file = os.path.join(dir_path, "test_bukuDb", "firefox_res.yaml")
res_nopt_yaml_file = os.path.join(dir_path, "test_bukuDb", "firefox_res_nopt.yaml")
ff_db_path = os.path.join(dir_path, "test_bukuDb", "places.sqlite")
if not os.path.isfile(ff_db_path):
tmp_zip = tmpdir.join("bookmarks.zip")
with urllib.request.urlopen(zip_url) as response, open(
tmp_zip.strpath, "wb"
) as out_file:
shutil.copyfileobj(response, out_file)
with zipfile.ZipFile(tmp_zip.strpath) as zip_obj:
zip_obj.extractall(path=os.path.join(dir_path, "test_bukuDb"))
return ff_db_path, res_yaml_file, res_nopt_yaml_file
@pytest.mark.parametrize("add_pt", [True, False])
def test_load_firefox_database(firefox_db, add_pt):
# compatibility
ff_db_path = firefox_db[0]
dump_data = False # NOTE: change this value to dump data
res_yaml_file = firefox_db[1] if add_pt else firefox_db[2]
if not dump_data:
with open(res_yaml_file, "r") as f:
res_yaml = yaml.load(f, Loader=PrettySafeLoader)
# init
import buku
bdb = buku.BukuDb()
bdb.add_rec = mock.Mock()
bdb.load_firefox_database(ff_db_path, None, add_pt)
call_args_list_dict = dict(bdb.add_rec.call_args_list)
# test
if not dump_data:
assert call_args_list_dict == res_yaml
if dump_data:
with open(res_yaml_file, "w") as f:
yaml.dump(call_args_list_dict, f)
print("call args list dict dumped to:{}".format(res_yaml_file))
@pytest.mark.parametrize(
"keyword_results, stag_results, exp_res",
[
([], [], []),
(["item1"], ["item1", "item2"], ["item1"]),
(["item2"], ["item1"], []),
],
)
def test_search_keywords_and_filter_by_tags(keyword_results, stag_results, exp_res):
"""test method."""
# init
import buku
bdb = buku.BukuDb()
bdb.searchdb = mock.Mock(return_value=keyword_results)
bdb.search_by_tag = mock.Mock(return_value=stag_results)
# test
res = bdb.search_keywords_and_filter_by_tags(
mock.Mock(), mock.Mock(), mock.Mock(), mock.Mock(), []
)
assert exp_res == res
@pytest.mark.parametrize(
"search_results, exclude_results, exp_res",
[
([], [], []),
(["item1", "item2"], ["item2"], ["item1"]),
(["item2"], ["item1"], ["item2"]),
(["item1", "item2"], ["item1", "item2"], []),
],
)
def test_exclude_results_from_search(search_results, exclude_results, exp_res):
"""test method."""
# init
import buku
bdb = buku.BukuDb()
bdb.searchdb = mock.Mock(return_value=exclude_results)
# test
res = bdb.exclude_results_from_search(search_results, [], True)
assert exp_res == res
def test_exportdb_empty_db():
with NamedTemporaryFile(delete=False) as f:
db = BukuDb(dbfile=f.name)
with NamedTemporaryFile(delete=False) as f2:
res = db.exportdb(f2.name)
assert not res
def test_exportdb_single_rec(tmpdir):
with NamedTemporaryFile(delete=False) as f:
db = BukuDb(dbfile=f.name)
db.add_rec("http://example.com")
exp_file = tmpdir.join("export")
db.exportdb(exp_file.strpath)
with open(exp_file.strpath) as f:
assert f.read()
def test_exportdb_to_db():
with NamedTemporaryFile(delete=False) as f1, NamedTemporaryFile(
delete=False, suffix=".db"
) as f2:
db = BukuDb(dbfile=f1.name)
db.add_rec("http://example.com")
db.add_rec("http://google.com")
with mock.patch("builtins.input", return_value="y"):
db.exportdb(f2.name)
db2 = BukuDb(dbfile=f2.name)
assert db.get_rec_all() == db2.get_rec_all()
@pytest.mark.parametrize(
"urls, exp_res",
[
[[], -1],
[["http://example.com"], 1],
[["htttp://example.com", "http://google.com"], 2],
],
)
def test_get_max_id(urls, exp_res):
with NamedTemporaryFile(delete=False) as f:
db = BukuDb(dbfile=f.name)
if urls:
list(map(lambda x: db.add_rec(x), urls))
assert db.get_max_id() == exp_res
# Helper functions for testcases
def split_and_test_membership(a, b):
# :param a, b: comma separated strings to split
# test everything in a in b
return all(x in b.split(",") for x in a.split(","))
def inclusive_range(start, end):
return list(range(start, end + 1))
def normalize_range(db_len, low, high):
"""normalize index and range.
Args:
db_len (int): database length.
low (int): low limit.
high (int): high limit.
Returns:
Tuple contain following normalized variables (low, high)
"""
require_comparison = True
# don't deal with non instance of the variable.
if not isinstance(low, int):
n_low = low
require_comparison = False
if not isinstance(high, int):
n_high = high
require_comparison = False
max_value = db_len
if low == "max" and high == "max":
n_low = db_len
n_high = max_value
elif low == "max" and high != "max":
n_low = high
n_high = max_value
elif low != "max" and high == "max":
n_low = low
n_high = max_value
else:
n_low = low
n_high = high
if require_comparison:
if n_high < n_low:
n_high, n_low = n_low, n_high
return (n_low, n_high)
if __name__ == "__main__":
unittest.main()