Skip to content
Snippets Groups Projects
test_diceware.py 10.23 KiB
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'