from __future__ import unicode_literals import datetime import os import pytest import sys from io import StringIO from diceware import ( WORDLISTS_DIR, RE_LANG_CODE, SPECIAL_CHARS, get_wordlist, get_wordlist_path, insert_special_char, get_passphrase, handle_options, main, __version__, print_version, ) class FakeRandom(object): # a very, very bad random generator. # Very handy for tests, though :-) nums_to_draw = [0] * 100 def choice(self, elems): num, self.nums_to_draw = self.nums_to_draw[0], self.nums_to_draw[1:] return elems[num] @pytest.fixture(scope="function") def argv_handler(request): """This fixture restores sys.argv and sys.stdin after tests. """ _argv_stored = sys.argv _stdin_stored = sys.stdin def teardown(): sys.argv = _argv_stored sys.stdin = _stdin_stored request.addfinalizer(teardown) class Test_GetWordList(object): def test_get_wordlist_en(self): # we can get a list of words out of english wordlist. en_src = os.path.join(WORDLISTS_DIR, 'wordlist_en.txt') with open(en_src, 'r') as fd: en_result = get_wordlist(fd) assert en_result[0] == 'a' assert en_result[-1] == '@' assert len(en_result) == 8192 def test_get_wordlist_simple(self, tmpdir): # simple wordlists can be created in_file = tmpdir.mkdir("work").join("mywordlist") in_file.write("a\nb\n") with open(in_file.strpath, 'r') as fd: result = get_wordlist(fd) assert ['a', 'b'] == result def test_get_wordlist_ignore_empty_lines(self, tmpdir): # we ignore empty lines in wordlists in_file = tmpdir.mkdir("work").join("mywordlist") in_file.write("\n\na\n\n") with open(in_file.strpath, 'r') as fd: result = get_wordlist(fd) assert ['a'] == result def test_get_wordlist_closes_fd(self, tmpdir): # we close passed-in file descriptors in_file = tmpdir.join("somewordlist") in_file.write("aaa\nbbb\n") with open(in_file.strpath, 'r') as fd: get_wordlist(fd) assert fd.closed is True class TestDicewareModule(object): def test_re_lang_code(self): # RE_LANG_CODE really works # valid stuff assert RE_LANG_CODE.match('de') is not None assert RE_LANG_CODE.match('DE') is not None assert RE_LANG_CODE.match('vb') is not None # invalid stuff assert RE_LANG_CODE.match('de_DE') is None assert RE_LANG_CODE.match('u1') is None assert RE_LANG_CODE.match('u') is None assert RE_LANG_CODE.match('dea') is None def test_get_wordlist_path(self): # we can get valid wordlist paths assert os.path.exists(get_wordlist_path('en')) assert not os.path.exists(get_wordlist_path('zz')) def test_get_wordlist_path_requires_ascii(self): # non ASCII alphabet chars are not accepted in language specifier with pytest.raises(ValueError) as exc_info: get_wordlist_path('../../tmp') assert exc_info.value.args[0].startswith( 'Not a valid language code') def test_get_wordlist_path_loweres_country_code(self): # upper case country codes are lowered assert os.path.basename(get_wordlist_path('de')) == 'wordlist_de.txt' assert os.path.basename(get_wordlist_path('De')) == 'wordlist_de.txt' assert os.path.basename(get_wordlist_path('DE')) == 'wordlist_de.txt' def test_insert_special_char(self): # we can insert special chars in words. fake_rnd = FakeRandom() result1 = insert_special_char('foo', specials='bar', rnd=fake_rnd) assert result1 == 'boo' fake_rnd.nums_to_draw = [1, 1] result2 = insert_special_char('foo', specials='bar', rnd=fake_rnd) assert result2 == 'fao' fake_rnd.nums_to_draw = [2, 2] result3 = insert_special_char('foo', specials='bar', rnd=fake_rnd) assert result3 == 'for' fake_rnd.nums_to_draw = [0, 0] result4 = insert_special_char('foo', rnd=fake_rnd) assert result4 == '~oo' def test_insert_special_char_defaults(self): # defaults are respected expected_matrix = [] for i in range(3): for j in range(len(SPECIAL_CHARS)): word = list('foo') word[i] = SPECIAL_CHARS[j] expected_matrix.append(''.join(word)) for x in range(100): assert insert_special_char('foo') in expected_matrix def test_special_chars_do_not_quote(self): # backslashes in SPECIAL_CHAR do not hide away chars assert len(SPECIAL_CHARS) == 36 def test_get_passphrase(self): # we can get passphrases r1 = get_passphrase() r2 = get_passphrase() assert r1 != r2 def test_get_passphrase_capitals(self): # by default a passphrase contains upper case chars phrase = get_passphrase() assert phrase.lower() != phrase def test_get_passphrase_no_capitals(self): # we can turn capitals off options = handle_options(args=[]) options.capitalize = False phrase = get_passphrase(options) assert phrase.lower() == phrase def test_get_passphrase_specialchars(self): # we can request special chars in passphrases options = handle_options(args=[]) options.specials = 2 phrase = get_passphrase(options) specials = [x for x in phrase if x in SPECIAL_CHARS] # the 2nd special char position might be equal to 1st. assert len(specials) > 0 def test_get_passphrase_delimiters(self): # we can set separators options = handle_options(args=[]) options.delimiter = " " phrase = get_passphrase(options) assert " " in phrase def test_get_passphrase_wordlist_fd(self): # we can pass in an own wordlist options = handle_options(args=[]) options.infile = StringIO("word1\n") phrase = get_passphrase(options) assert "Word1" in phrase def test_print_version(self, capsys): # we can print version infos print_version() out, err = capsys.readouterr() assert err == '' assert __version__ in out def test_print_version_current_year(self, capsys): # in version infos we display the current year print_version() expected = '(C) %s' % (datetime.datetime.now().year) out, err = capsys.readouterr() assert expected in out def test_handle_options(self): # we can get help with pytest.raises(SystemExit) as exc_info: handle_options(['--help']) assert exc_info.value.code == 0 def test_handle_options_defaults(self): # defaults are correctly set options = handle_options([]) assert options.num == 6 assert options.capitalize is True assert options.specials == 0 assert options.infile is None assert options.version is False assert options.delimiter == "" def test_handle_options_infile(self, tmpdir): # we can give an infile tmpdir.chdir() with open('mywords', 'w') as fd: fd.write('one\ntwo\n') options = handle_options(['mywords', ]) assert options.infile is not None assert options.infile.read() == 'one\ntwo\n' def test_handle_options_version(self): # we can ask for version infos options = handle_options(['--version', ]) assert options.version is True def test_handle_options_delimiter(self): # we can set delimiter options = handle_options(['-d', ' ']) assert options.delimiter == ' ' options = handle_options(['--delimiter', ' ']) assert options.delimiter == ' ' options = handle_options(['-d', 'WOW']) assert options.delimiter == 'WOW' def test_main(self, capsys): # we can get a passphrase main([]) # call with default options in place out, err = capsys.readouterr() assert err == '' # we got no errors assert out[-1] == '\n' # output ends with liebreak assert not ('\n' in out[:-1]) # we get one line assert len(out) > 5 # we get at least some chars def test_main_help(self, argv_handler, capsys): # we can get help sys.argv = ['diceware', '--help'] with pytest.raises(SystemExit) as exc_info: main() assert exc_info.value.code == 0 out, err = capsys.readouterr() expected_path = os.path.join( os.path.dirname(__file__), 'exp_help_output.txt') with open(expected_path, 'r') as fd: expected_output = fd.read() assert out == expected_output def test_main_version(self, argv_handler, capsys): # we can get version infos. sys.argv = ['diceware', '--version'] with pytest.raises(SystemExit) as exc_info: main() assert exc_info.value.code == 0 out, err = capsys.readouterr() assert __version__ in out def test_main_argv(self, argv_handler): # main() handles sys.argv if nothing is provided sys.argv = ['diceware', '--help'] with pytest.raises(SystemExit) as exc_info: main() assert exc_info.value.code == 0 def test_main_infile(self, argv_handler, tmpdir, capsys): # main() reads custom wordlist if provided custom_path = tmpdir.join('mywordlist.txt') custom_path.write('mysingleword\n') tmpdir.chdir() main(['-n', '1', 'mywordlist.txt', ]) out, err = capsys.readouterr() assert out == 'Mysingleword\n' def test_main_infile_stdin(self, argv_handler, capsys): # main() also accepts input from stdin sys.stdin = StringIO("word1\n") sys.argv = ['diceware', '-n', '2', '-'] main() out, err = capsys.readouterr() assert out == 'Word1Word1\n' def test_main_delimiters(self, argv_handler, capsys): # delimiters are respected on calls to main sys.stdin = StringIO("word1\n") sys.argv = ['diceware', '-n', '2', '-d', 'DELIM', '-'] main() out, err = capsys.readouterr() assert out == 'Word1DELIMWord1\n'