From 8fc2775b48e03b418b607d6b8063a406deda5d26 Mon Sep 17 00:00:00 2001 From: rachmadani haryono Date: Sat, 18 Mar 2017 21:41:59 +0800 Subject: [PATCH] Feature/test (#130) * new: test: test for BukuHTMLParser class * new: test: test for BukuCrypt class * chg: test: change getpass patch path * new: test: test for ExtendedArgumentParser class * new: test: test for functions. * chg: test: fix warning for caplog * chg: test: exclude some test for python3.5 only --- tests/test_BukuCrypt.py | 35 +++ tests/test_BukuHTMLParser.py | 66 +++++ tests/test_ExtendedArgumentParser.py | 64 ++++ tests/test_buku.py | 424 +++++++++++++++++++++++++++ tests/test_bukuDb.py | 2 +- 5 files changed, 590 insertions(+), 1 deletion(-) create mode 100644 tests/test_BukuCrypt.py create mode 100644 tests/test_BukuHTMLParser.py create mode 100644 tests/test_ExtendedArgumentParser.py create mode 100644 tests/test_buku.py diff --git a/tests/test_BukuCrypt.py b/tests/test_BukuCrypt.py new file mode 100644 index 0000000..cbc89a6 --- /dev/null +++ b/tests/test_BukuCrypt.py @@ -0,0 +1,35 @@ +"""test module.""" +from unittest import mock +import os + +import pytest + + +def test_get_filehash(tmpdir): + """test method.""" + exp_res = b'\x9f\x86\xd0\x81\x88L}e\x9a/\xea\xa0\xc5Z\xd0\x15\xa3\xbfO\x1b+\x0b\x82,\xd1]l\x15\xb0\xf0\n\x08' # NOQA + test_file = os.path.join(tmpdir.strpath, 'my_test_file.txt') + with open(test_file, 'w') as f: + f.write('test') + from buku import BukuCrypt + res = BukuCrypt.get_filehash(test_file) + assert res == exp_res + + +def touch(fname): + """touch implementation for python.""" + if os.path.exists(fname): + os.utime(fname, None) + else: + open(fname, 'a').close() + + +def test_encrypt_decrypt(tmpdir): + """test method.""" + dbfile = os.path.join(tmpdir.strpath, 'test_encrypt_decrypt_dbfile') + touch(dbfile) + with mock.patch('getpass.getpass', return_value='password'): + from buku import BukuCrypt + with pytest.raises(SystemExit): + BukuCrypt.encrypt_file(1, dbfile=dbfile) + BukuCrypt.decrypt_file(1, dbfile=dbfile) diff --git a/tests/test_BukuHTMLParser.py b/tests/test_BukuHTMLParser.py new file mode 100644 index 0000000..1544675 --- /dev/null +++ b/tests/test_BukuHTMLParser.py @@ -0,0 +1,66 @@ +"""test module.""" +from itertools import product +from unittest import mock + +import pytest + + +def test_init(): + """test method.""" + from buku import BukuHTMLParser + obj = BukuHTMLParser() + assert not obj.in_title_tag + assert not obj.data + assert obj.prev_tag is None + assert obj.parsed_title is None + + +@pytest.mark.parametrize('tag', ['', 'title']) +def test_handle_starttag(tag): + """test method.""" + attrs = mock.Mock() + from buku import BukuHTMLParser + obj = BukuHTMLParser() + obj.handle_starttag(tag, attrs) + if tag == 'title': + assert obj.in_title_tag + assert obj.prev_tag == tag + else: + assert not obj.in_title_tag + + +@pytest.mark.parametrize('tag, data', product(['', 'title'], [None, 'data'])) +def test_handle_endtag(tag, data): + """test method.""" + from buku import BukuHTMLParser + obj = BukuHTMLParser() + obj.data = data + obj.reset = mock.Mock() + obj.handle_endtag(tag) + # test + if tag == 'title': + assert not obj.in_title_tag + if tag == 'title' and data != '': + assert obj.parsed_title == data + obj.reset.assert_called_once_with() + + +@pytest.mark.parametrize('prev_tag, in_title_tag', product(['', 'title'], [None, 'data'])) +def test_handle_data(prev_tag, in_title_tag): + """test method.""" + new_data = 'new_data' + from buku import BukuHTMLParser + obj = BukuHTMLParser() + obj.prev_tag = prev_tag + obj.data = '' + obj.in_title_tag = in_title_tag + obj.handle_data(new_data) + if obj.prev_tag == 'title' and in_title_tag: + assert obj.data == new_data + + +def test_error(): + """test method.""" + from buku import BukuHTMLParser + obj = BukuHTMLParser() + obj.error(message=mock.Mock()) diff --git a/tests/test_ExtendedArgumentParser.py b/tests/test_ExtendedArgumentParser.py new file mode 100644 index 0000000..bb1f597 --- /dev/null +++ b/tests/test_ExtendedArgumentParser.py @@ -0,0 +1,64 @@ +"""test module.""" +from itertools import product +from unittest import mock + +import pytest + + +@pytest.mark.parametrize("platform, file", product(['win32', 'linux'], [None, mock.Mock()])) +def test_program_info(platform, file): + """test method.""" + with mock.patch('buku.sys') as m_sys: + import buku + prog_info_text = ''' +SYMBOLS: + > title + + comment + # tags + +Version {} +Copyright © 2015-2017 {} +License: {} +Webpage: https://github.com/jarun/Buku +'''.format(buku.__version__, buku.__author__, buku.__license__) + file = mock.Mock() + if file is None: + buku.ExtendedArgumentParser.program_info() + else: + buku.ExtendedArgumentParser.program_info(file) + if platform == 'win32' and file == m_sys.stdout: + m_sys.stderr.write.assert_called_once_with(prog_info_text) + else: + file.write.assert_called_once_with(prog_info_text) + + +def test_prompt_help(): + """test method.""" + file = mock.Mock() + import buku + buku.ExtendedArgumentParser.prompt_help(file) + file.write.assert_called_once_with(''' +keys: + 1-N browse search result indices and/or ranges + a open all results in browser + s keyword [...] search for records with ANY keyword + 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 tag list + w [editor|index] edit and add or update a bookmark + (tag list index fetches bookmarks by tag) + ? show this help + q, ^D, double Enter exit buku + +''') + + +def test_print_help(): + """test method.""" + file = mock.Mock() + import buku + obj = buku.ExtendedArgumentParser() + obj.program_info = mock.Mock() + obj.print_help(file) + obj.program_info.assert_called_once_with(file) diff --git a/tests/test_buku.py b/tests/test_buku.py new file mode 100644 index 0000000..97cc7c3 --- /dev/null +++ b/tests/test_buku.py @@ -0,0 +1,424 @@ +"""test module.""" +from itertools import product +from unittest import mock +import sys + +import pytest + +only_python_3_5 = pytest.mark.skipif(sys.version_info < (3, 5), reason="requires python3.5") + + +@pytest.mark.parametrize( + 'url, exp_res', + [ + ('http://example.com', False), + ] +) +def test_is_bad_url(url, exp_res): + """test func.""" + import buku + assert exp_res == buku.is_bad_url(url) + + +@pytest.mark.parametrize( + 'url, exp_res', + [ + ('http://example.com/file.pdf', True), + ('http://example.com/file.txt', True), + ('http://example.com/file.jpg', False), + ] +) +def test_is_ignored_mime(url, exp_res): + """test func.""" + import buku + assert exp_res == buku.is_ignored_mime(url) + + +def test_get_page_title(): + """test func.""" + resp = mock.Mock() + parser = mock.Mock() + with mock.patch('buku.BukuHTMLParser', return_value=parser): + import buku + res = buku.get_page_title(resp) + assert res == parser.parsed_title + + +def test_gen_headers(): + """test func.""" + import buku + exp_myheaders = { + 'Accept-Encoding': 'gzip,deflate', + 'User-Agent': buku.USER_AGENT, + 'Accept': '*/*', + 'Cookie': '', + 'DNT': '1' + } + buku.gen_headers() + assert buku.myproxy is None + assert buku.myheaders == exp_myheaders + + +@pytest.mark.parametrize('m_myproxy', [None, mock.Mock()]) +def test_get_PoolManager(m_myproxy): + """test func.""" + with mock.patch('buku.urllib3') as m_ul3: + import buku + buku.myproxy = m_myproxy + res = buku.get_PoolManager() + if m_myproxy: + m_ul3.ProxyManager.assert_called_once_with( + m_myproxy, num_pools=1, headers=buku.myheaders) + assert res == m_ul3.ProxyManager.return_value + else: + m_ul3.PoolManager.assert_called_once_with( + num_pools=1, headers=buku.myheaders) + assert res == m_ul3.PoolManager.return_value + + +@pytest.mark.parametrize( + 'keywords, exp_res', + [ + (None, None), + ([], None), + (['tag1', 'tag2'], ',tag1 tag2,'), + (['tag1,tag2', 'tag3'], ',tag1,tag2 tag3,'), + ] +) +def test_parse_tags(keywords, exp_res): + """test func.""" + import buku + if keywords is None: + pass + elif not keywords: + exp_res = buku.DELIM + res = buku.parse_tags(keywords) + assert res == exp_res + + +@pytest.mark.parametrize( + 'nav, is_editor_valid_retval, edit_rec_retval', + product( + ['w', [None, None, 1], [None, None, 'string']], + [True, False], + [[mock.Mock(), mock.Mock(), mock.Mock(), mock.Mock()], None] + ) +) +def test_edit_at_prompt(nav, is_editor_valid_retval, edit_rec_retval): + """test func.""" + obj = mock.Mock() + editor = mock.Mock() + with mock.patch('buku.get_system_editor', return_value=editor), \ + mock.patch('buku.is_editor_valid', return_value=is_editor_valid_retval), \ + mock.patch('buku.edit_rec', return_value=edit_rec_retval) as m_edit_rec: + import buku + buku.edit_at_prompt(obj, nav) + # test + if nav == 'w' and not is_editor_valid_retval: + return + elif nav == 'w': + m_edit_rec.assert_called_once_with(editor, '', None, buku.DELIM, None) + elif buku.is_int(nav[2:]): + obj.edit_update_rec.assert_called_once_with(int(nav[2:])) + return + else: + editor = nav[2:] + m_edit_rec.assert_called_once_with(editor, '', None, buku.DELIM, None) + if edit_rec_retval is not None: + obj.add_rec(*edit_rec_retval) + + +@only_python_3_5 +@pytest.mark.parametrize( + 'idx, row0, row1, row2, row3, row4, row5', + product( + [0, 1], + [0], + ['', 'row1'], + ['', 'row2'], + [',', 'row3'], + ['', 'row4'], + [0, 1], + ) +) +def test_print_record(idx, row0, row1, row2, row3, row4, row5): + """test func.""" + row = [row0, row1, row2, row3, row4, row5] + pr = None + with mock.patch('buku.print') as m_print: + import buku + buku.print_record(row, idx) + + if idx != 0: + pr = buku.ID_str % (idx, row1, row0) + else: + pr = buku.ID_DB_str % (row0, row1) + if row5 & 1: + pr = buku.MUTE_str % (pr) + else: + pr += '\n' + + if row2 != '': + pr = buku.TITLE_str % (pr, row2) + if row4 != '': + pr = buku.DESC_str % (pr, row4) + if row3 != buku.DELIM: + pr = buku.TAG_str % (pr, row3[1:-1]) + + m_print.assert_called_once_with(pr) + + +@pytest.mark.parametrize( + 'field_filter, single_record', + product(range(4), [True, False]) +) +def test_format_json(field_filter, single_record): + """test func.""" + resultset = [['row{}'.format(x) for x in range(5)]] + if field_filter == 1: + marks = {'uri': 'row1'} + elif field_filter == 2: + marks = {'uri': 'row1', 'tags': 'row3'[1:-1]} + elif field_filter == 3: + marks = {'title': 'row2'} + else: + marks = { + 'index': 'row0', + 'uri': 'row1', + 'title': 'row2', + 'description': 'row4', + 'tags': 'row3'[1:-1] + } + if not single_record: + marks = [marks] + + with mock.patch('buku.json') as m_json: + import buku + res = buku.format_json(resultset, single_record, field_filter) + m_json.dumps.assert_called_once_with(marks, sort_keys=True, indent=4) + assert res == m_json.dumps.return_value + + +@pytest.mark.parametrize( + 'string, exp_res', + [ + ('string', False), + ('12', True), + ('12.1', False), + ] +) +def test_is_int(string, exp_res): + """test func.""" + import buku + assert exp_res == buku.is_int(string) + + +@pytest.mark.parametrize( + 'url, opened_url', + [ + ['http://example.com', 'http://example.com', ], + ['example.com', 'http://example.com', ], + ] +) +def test_browse(url, opened_url): + """test func.""" + with mock.patch('buku.webbrowser') as m_webbrowser: + import buku + buku.browse(url) + m_webbrowser.open.assert_called_once_with(opened_url) + + +@only_python_3_5 +@pytest.mark.parametrize( + 'status_code, latest_release', + product([200, 404], [True, False]) +) +def test_check_upstream_release(status_code, latest_release): + """test func.""" + resp = mock.Mock() + resp.status_code = status_code + with mock.patch('buku.requests') as m_requests, \ + mock.patch('buku.print') as m_print: + import buku + if latest_release: + latest_version = 'v{}'.format(buku.__version__) + else: + latest_version = 'v0' + resp.json.return_value = [{'tag_name': latest_version}] + m_requests.get.return_value = resp + buku.check_upstream_release() + if status_code != 200: + return + if latest_release: + print_text = 'This is the latest release' + else: + print_text = 'Latest upstream release is %s' % latest_version + m_print.assert_called_once_with(print_text) + + +@pytest.mark.parametrize( + 'exp, item, exp_res', + [ + ('cat.y', 'catty', True), + ('cat.y', 'caty', False), + ] +) +def test_regexp(exp, item, exp_res): + """test func.""" + import buku + res = buku.regexp(exp, item) + assert res == exp_res + + +@pytest.mark.parametrize('token, exp_res', [('text', ',text,')]) +def test_delim_wrap(token, exp_res): + """test func.""" + import buku + res = buku.delim_wrap(token) + assert res == exp_res + + +@only_python_3_5 +def test_read_in(): + """test func.""" + message = mock.Mock() + with mock.patch('buku.disable_sigint_handler'), \ + mock.patch('buku.enable_sigint_handler'), \ + mock.patch('buku.input', return_value=message): + import buku + res = buku.read_in(msg=mock.Mock()) + assert res == message + + +def test_sigint_handler(): + """test func.""" + with mock.patch('buku.os') as m_os: + import buku + buku.sigint_handler(mock.Mock(), mock.Mock()) + m_os._exit.assert_called_once_with(1) + + +def test_get_system_editor(): + """test func.""" + with mock.patch('buku.os') as m_os: + import buku + res = buku.get_system_editor() + assert res == m_os.environ.get.return_value + m_os.environ.get.assert_called_once_with('EDITOR', 'none') + + +@pytest.mark.parametrize( + 'editor, exp_res', + [ + ('none', False), + ('0', False), + ('random_editor', True), + ] +) +def test_is_editor_valid(editor, exp_res): + """test func.""" + import buku + assert buku.is_editor_valid(editor) == exp_res + + +@pytest.mark.parametrize( + 'url, title_in, tags_in, desc', + product( + [None, 'example.com'], + [None, '', 'title'], + ['', 'tag1,tag2', ',tag1,tag2,'], + [None, '', 'description'], + ) +) +def test_to_temp_file_content(url, title_in, tags_in, desc): + """test func.""" + import buku + res = buku.to_temp_file_content(url, title_in, tags_in, desc) + lines = [ + '# Lines beginning with "#" will be stripped.', + '# Add URL in next line (single line).', + '# Add TITLE in next line (single line). Leave blank to web fetch, "-" for no title.', + '# Add comma-separated TAGS in next line (single line).', + '# Add COMMENTS in next line(s).', + ] + idx_offset = 0 + # url + if url is not None: + lines.insert(2, url) + idx_offset += 1 + if title_in is None: + title_in = '' + elif title_in == '': + title_in = '-' + else: + pass + + # title + lines.insert(idx_offset + 3, title_in) + idx_offset += 1 + + # tags + lines.insert(idx_offset + 4, tags_in.strip(buku.DELIM)) + idx_offset += 1 + + # description + if desc is not None and desc != '': + pass + else: + desc = '' + lines.insert(idx_offset + 5, desc) + + for idx, res_line in enumerate(res.splitlines()): + assert lines[idx] == res_line + + +@pytest.mark.parametrize( + 'content, exp_res', + [ + ('', None), + ('#line1\n#line2', None), + ( + '\n'.join([ + 'example.com', + 'title', + 'tags', + 'desc', + ]), + ('example.com', 'title', ',tags,', 'desc') + ) + ] +) +def test_parse_temp_file_content(content, exp_res): + """test func.""" + import buku + res = buku.parse_temp_file_content(content) + assert res == exp_res + + +@only_python_3_5 +@pytest.mark.skip(reason="can't patch subprocess") +def test_edit_rec(): + """test func.""" + editor = 'nanoe' + args = ('url', 'title_in', 'tags_in', 'desc') + with mock.patch('buku.to_temp_file_content'), \ + mock.patch('buku.os'), \ + mock.patch('buku.open'), \ + mock.patch('buku.parse_temp_file_content') as m_ptfc: + import buku + res = buku.edit_rec(editor, *args) + assert res == m_ptfc.return_value + + +@pytest.mark.parametrize('argv, pipeargs, isatty', product(['argv'], [None, []], [True, False])) +def test_piped_input(argv, pipeargs, isatty): + """test func.""" + with mock.patch('buku.sys') as m_sys: + m_sys.stdin.isatty.return_value = isatty + m_sys.stdin.readlines.return_value = 'arg1\narg2' + import buku + if pipeargs is None and not isatty: + with pytest.raises(TypeError): + buku.piped_input(argv, pipeargs) + return + buku.piped_input(argv, pipeargs) diff --git a/tests/test_bukuDb.py b/tests/test_bukuDb.py index c4c8681..e5b8dd9 100644 --- a/tests/test_bukuDb.py +++ b/tests/test_bukuDb.py @@ -424,7 +424,7 @@ def test_print_rec(capsys, caplog, setup): bdb.print_rec(1) out, err = capsys.readouterr() - for record in caplog.records(): + for record in caplog.records: assert record.levelname == "ERROR" assert record.getMessage() == "No matching index 1" assert (out, err) == ('', '')