<commit_before>import argparse
import inspect
import os
import shutil
import sys
import unittest
import yaml
import time
from functools import wraps

try:
    from unittest.mock import patch, Mock
    from io import StringIO
except ImportError:
    # backwards compatbility with Python2
    from mock import patch, Mock
    from StringIO import StringIO

if sys.version_info >= (3,):
    BUILTINS_NAME = "builtins"
else:
    BUILTINS_NAME = "__builtin__"

import gnupg


__file__ = os.path.relpath(inspect.getsourcefile(lambda _: None))

TEST_DIR = os.path.join(os.path.dirname(os.path.relpath(__file__)))
TEST_DATA_DIR = os.path.join(TEST_DIR, "data")

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.relpath(__file__))))
import pysswords
from pysswords import __main__
from pysswords.db import Database, Credential

PROFILE = False


def timethis(func):
    ''' Decorator that reports the execution time.
    '''
    if PROFILE:
        @wraps(func)
        def wrapper(*args, **kwargs):
            start = time.time()
            result = func(*args, **kwargs)
            end = time.time()
            print("[{:.2f}]".format(end-start), func.__name__)
            return result
        return wrapper
    else:
        return func


def build_keys():
    gpg = gnupg.GPG(homedir="/tmp/pysswords")
    key_input = gpg.gen_key_input(
        name_real="Pysswords",
        name_email="<EMAIL>",
        name_comment="Auto-generated by Pysswords",
        key_length=512,
        expire_date=0,
        passphrase="<PASSWORD>"
    )
    key = gpg.gen_key(key_input)
    ascii_armored_public_keys = gpg.export_keys(key)
    ascii_armored_private_keys = gpg.export_keys(key, True)
    with open(os.path.join(TEST_DATA_DIR, "newkey.asc"), 'w') as f:
        f.write(ascii_armored_public_keys)
        f.write(ascii_armored_private_keys)


def mock_create_keyring(path, *args, **kwargs):
    """Import key.asc instead of generating new key
    passphrase used to create the key was '<PASSWORD>'"""
    keyring_path = os.path.join(path, ".keys")
    gpg = gnupg.GPG(homedir=keyring_path)
    with open(os.path.join(TEST_DATA_DIR, "key.asc")) as keyfile:
        gpg.import_keys(keyfile.read())
    return gpg.list_keys()[0]


def mock_gen_key(self, key_input):
    return mock_create_keyring(self.homedir)


def some_credential(**kwargs):
    return pysswords.db.Credential(
        name=kwargs.get("name", "example.com"),
        login=kwargs.get("login", "john.doe"),
        password=kwargs.get("password", <PASSWORD>-- X --END <PASSWORD>--"),
        comment=kwargs.get("comment", "Some comments"),
    )


# @patch("pysswords.crypt.create_keyring", new=mock_create_keyring)
class CryptTests(unittest.TestCase):

    def setUp(self):
        self.path = os.path.join(TEST_DATA_DIR, "database")
        self.passphrase = "<PASSWORD>"
        self.cleanup()

    def tearDown(self):
        self.cleanup()

    def cleanup(self):
        if os.path.exists(self.path):
            shutil.rmtree(self.path)

    @timethis
    @patch("pysswords.crypt.create_keyring", new=mock_create_keyring)
    def test_create_keyring_adds_gpg_keys_to_path(self):
        pysswords.crypt.create_keyring(self.path, self.passphrase)
        pubring = os.path.join(self.path, ".keys", "pubring.gpg")
        secring = os.path.join(self.path, ".keys", "secring.gpg")
        self.assertTrue(os.path.isfile(pubring))
        self.assertTrue(os.path.isfile(secring))

    @timethis
    @patch("pysswords.crypt.create_keyring", new=mock_create_keyring)
    def test_create_keyring_adds_key_to_keyring(self):
        database = Database(self.path)
        pysswords.crypt.create_keyring(self.path, self.passphrase)
        gpg = gnupg.GPG(homedir=database.keys_path)
        self.assertEqual(1, len(gpg.list_keys()))

    @timethis
    @patch("pysswords.crypt.gnupg.GPG.gen_key", new=mock_gen_key)
    def test_generate_keys_return_valid_key(self):
        key = pysswords.crypt.generate_keys(self.path, self.passphrase)
        self.assertIsNotNone(key)
        self.assertEqual(key["fingerprint"],
                         '<KEY>')
    @timethis
    def test_generate_key_input_returns_batch_string_with_passphrase(self):
        batch = pysswords.crypt.generate_key_input(self.path, self.passphrase)
        self.assertIn("\nPassphrase: {}".format(self.passphrase), batch)

    @timethis
    def test_create_keyring_generate_keys(self):
        self.cleanup()
        with patch("pysswords.crypt.generate_keys") as mocked_generate:
            pysswords.crypt.create_keyring(self.path, self.passphrase)
            self.assertTrue(mocked_generate.called)


@patch("pysswords.db.database.create_keyring", new=mock_create_keyring)
class DatabaseTests(unittest.TestCase):

    __name__ = "DatabaseTests"

    def setUp(self):
        self.path = os.path.join(TEST_DATA_DIR, "database")
        self.passphrase = "<PASSWORD>"
        self.cleanup()

    def tearDown(self):
        self.cleanup()

    def cleanup(self):
        if os.path.exists(self.path):
            shutil.rmtree(self.path)
    @timethis
    def test_create_makedirs_at_path(self):
        test_path = os.path.join(self.path, "creation")
        if os.path.exists(self.path):
            shutil.rmtree(self.path)
        pysswords.db.Database.create(test_path, self.passphrase)
        self.assertTrue(os.path.exists(test_path))

    @timethis
    def test_create_keyring(self):
        database = Database.create(self.path, self.passphrase)
        self.assertIsInstance(database, pysswords.db.Database)
        self.assertTrue(len(database.gpg.list_keys()) == 1)

    @timethis
    def test_keys_path_returns_database_path_joined_with_dot_keys(self):
        database = Database.create(self.path, self.passphrase)
        keys_path = database.keys_path
        self.assertEqual(keys_path, os.path.join(self.path, ".keys"))

    @timethis
    def test_add_credential_make_dir_in_dbpath_with_credential_name(self):
        database = Database.create(self.path, self.passphrase)
        database.add(some_credential())
        credential_dir = os.path.join(self.path, some_credential().name)
        self.assertTrue(os.path.exists(credential_dir))
        self.assertTrue(os.path.isdir(credential_dir))

    @timethis
    def test_add_credential_createas_pyssword_file_named_after_login(self):
        database = Database.create(self.path, self.passphrase)
        credential = some_credential()
        database.add(credential)
        credential_dir = os.path.join(self.path, credential.name)
        credential_filename = "{}.pyssword".format(credential.login)
        credential_file = os.path.join(credential_dir, credential_filename)
        self.assertTrue(os.path.isfile(credential_file))
        with open(credential_file) as f:
            self.assertEqual(yaml.load(f.read()), credential)

    @timethis
    def test_add_credential_creates_dir_when_credential_name_is_a_dir(self):
        database = Database.create(self.path, self.passphrase)
        credential = some_credential(name="emails/misc/example.com")
        emails_dir = os.path.join(self.path, "emails")
        misc_dir = os.path.join(emails_dir, "misc")
        database.add(credential)
        self.assertTrue(os.path.isdir(emails_dir))
        self.assertTrue(os.path.isdir(misc_dir))

    @timethis
    def test_add_credential_returns_credential_path(self):
        database = Database.create(self.path, self.passphrase)
        credential = some_credential()
        credential_path = database.add(credential)
        expected_path = os.path.join(
            self.path,
            os.path.basename(credential.name),
            "{}.pyssword".format(credential.login)
        )
        self.assertEqual(credential_path, expected_path)

    @timethis
    def test_gpg_returns_valid_gnupg_gpg_object(self):
        database = Database.create(self.path, self.passphrase)
        gpg = database.gpg
        self.assertIsInstance(gpg, pysswords.db.database.gnupg.GPG)

    @timethis
    def test_credentials_returns_a_list_of_all_added_credentials(self):
        database = Database.create(self.path, self.passphrase)
        database.add(some_credential(name="example.com"))
        database.add(some_credential(name="archive.org"))
        credentials = database.credentials
        self.assertIsInstance(credentials, list)
        self.assertEqual(2, len(credentials))
        for credential in credentials:
            self.assertIsInstance(credential, pysswords.db.Credential)

    @timethis
    def test_add_repeated_credential_without_overwrite_on_raises_error(self):
        database = Database.create(self.path, self.passphrase)
        credential = some_credential()
        database.add(credential)
        with self.assertRaises(pysswords.db.CredentialExistsError):
            database.add(credential)

    @timethis
    def test_remove_deletes_pysswords_file(self):
        database = Database.create(self.path, self.passphrase)
        credential = some_credential()
        credential_path = pysswords.db.credential.expandpath(
            self.path,
            credential)
        database.add(credential)
        self.assertTrue(os.path.isfile(credential_path))
        database.remove(credential)
        self.assertFalse(os.path.isfile(credential_path))

    @timethis
    def test_remove_deletes_pyssword_dir_if_empty_after_deletion(self):
        database = Database.create(self.path, self.passphrase)
        credential = some_credential()
        credential_path = pysswords.db.credential.expandpath(
            self.path,
            credential)

        database.add(credential)
        self.assertTrue(os.path.exists(os.path.dirname(credential_path)))
        database.remove(credential)
        self.assertFalse(os.path.exists(os.path.dirname(credential_path)))

    @timethis
    def test_get_credential_by_name_returns_expected_credential(self):
        database = Database.create(self.path, self.passphrase)
        credential = some_credential(name="example.com")
        database.add(credential)
        found = database.credential(name=credential.name)

        self.assertIsInstance(found, list)
        self.assertTrue(all(True for c in found
                            if isinstance(c, pysswords.db.Credential)))
        self.assertTrue(any(True for c in found
                            if c == credential))

    @timethis
    def test_get_returns_unique_credential_when_login_is_passed(self):
        database = Database.create(self.path, self.passphrase)
        credential = some_credential(name="example.com")
        credential2 = some_credential(name="example.com", login="jonny.doe")
        database.add(credential)
        database.add(credential2)
        found = database.credential(name=credential.name,
                                    login=credential.login)
        self.assertEqual(found, [credential])

    @timethis
    def test_get_returns_no_element_when_name_not_found(self):
        database = Database.create(self.path, self.passphrase)
        credential = some_credential(name="example.com")
        database.add(credential)
        found = database.credential(name="not added")
        self.assertListEqual(found, [])

    @timethis
    def test_search_database_returns_list_with_matched_credentials(self):
        database = Database.create(self.path, self.passphrase)
        database.add(some_credential(name="example.com"))
        database.add(some_credential(name="github.com"))
        database.add(some_credential(name="twitter.com"))

        self.assertEqual(len(database.search("it")), 2)
        self.assertEqual(len(database.search("github")), 1)
        self.assertEqual(len(database.search("not there")), 0)

    @timethis
    def test_encrypt_text_returns_valid_encryption_ascii_gpg(self):
        database = Database.create(self.path, self.passphrase)
        text = "secret"
        encrypted = database.encrypt(text)
        self.assertIn("-BEGIN PGP MESSAGE-", encrypted)
        self.assertIn("-END PGP MESSAGE-", encrypted)

    @timethis
    def test_key_returns_expected_key_fingerprint(self):
        database = Database.create(self.path, self.passphrase)
        self.assertEqual(
            database.key(),
            "<KEY>")

    @timethis
    def test_key_returns_private_key_when_private_is_true(self):
        to_patch = "pysswords.db.database.gnupg.GPG.list_keys"
        with patch(to_patch) as mocked_list_keys:
            mocked_list_keys.return_value = [
                {"fingerprint": "<KEY>"}
            ]
            database = Database.create(self.path, self.passphrase)
            database.key(private=True)
            database.gpg.list_keys.assert_any_call_with(secret=True)
        self.assertEqual(
            database.key(private=True),
            "2B88BF1F03FC2E3871894966F77B7A363E2EAE61")

    @timethis
    def test_decrypt_returns_plain_text_data(self):
        database = Database.create(self.path, self.passphrase)
        text = "secret"
        encrypted = database.encrypt(text)
        decrypted = database.decrypt(encrypted, passphrase=self.passphrase)
        self.assertEqual(decrypted, text)

    @timethis
    def test_update_credential_updates_credential_values(self):
        database = Database.create(self.path, self.passphrase)
        values = {
            "name": "example.com",
            "login": "jonh.doe",
            "password": "<PASSWORD>",
            "comment": "No Comments"
        }
        credential = some_credential(**values)
        database.add(credential)
        new_values = values
        new_values["login"] = "doe.john"
        database.update(credential, **new_values)
        found = database.credential(name=values["name"])[0]
        self.assertEqual(found._asdict(), new_values)


class CredentialTests(unittest.TestCase):

    def setUp(self):
        self.path = os.path.join(TEST_DATA_DIR, "database")
        self.cleanup()

    def tearDown(self):
        self.cleanup()

    def cleanup(self):
        if os.path.exists(self.path):
            shutil.rmtree(self.path)

    @timethis
    def test_credential_expandpath_returns_expected_path_to_credential(self):
        credential = some_credential()
        credential_path = pysswords.db.credential.expandpath(
            self.path,
            credential
        )
        expected_path = os.path.join(
            self.path,
            os.path.basename(credential.name),
            "{}.pyssword".format(credential.login)
        )
        self.assertEqual(credential_path, expected_path)

    @timethis
    def test_credential_content_returns_yaml_content_parseable_to_dict(self):
        content = pysswords.db.credential.content(some_credential())
        self.assertEqual(yaml.load(content), some_credential())


class UtilsTests(unittest.TestCase):

    @timethis
    def test_which_handle_windows_exe_extension_for_executables(self):
        with patch("pysswords.utils.os") as mocker:
            mocker.name = "nt"
            mocker.environ = {"PATH": "/"}
            mocker.pathsep = ":"
            mocked_join = Mock()
            mocker.path.join = mocked_join
            pysswords.utils.which("python")
            mocked_join.assert_any_call("/", "python.exe")


class ConsoleInterfaceTests(unittest.TestCase):

    def setUp(self):
        self.tempdb_path = os.path.join(TEST_DATA_DIR, "tmp")
        self.cleanup()
        self.passphrase = "<PASSWORD>"

    def tearDown(self):
        self.cleanup()

    def cleanup(self):
        if os.path.exists(self.tempdb_path):
            shutil.rmtree(self.tempdb_path)

    def create_database(self):
        with patch("pysswords.db.database.create_keyring",
                   new=mock_create_keyring):
            return Database.create(self.tempdb_path, self.passphrase)

    @timethis
    def test_cli_parse_args_returns_argparse_namespace(self):
        args = pysswords.__main__.parse_args(["--init"])
        self.assertIsInstance(args, argparse.Namespace)

    @timethis
    def test_cli_default_pyssword_dir(self):
        pysswords_dir = os.path.join(os.path.expanduser("~"), ".pysswords")
        self.assertEqual(pysswords_dir, pysswords.__main__.default_db())

    @timethis
    def test_cli_parse_args_has_init_arg(self):
        args = pysswords.__main__.parse_args(["--init"])
        self.assertIn("init", args.__dict__)
        args_short = pysswords.__main__.parse_args(["-I"])
        self.assertIn("init", args_short.__dict__)

    @timethis
    def test_cli_parse_args_has_database_arg(self):
        args = pysswords.__main__.parse_args(["--database", "/tmp/pysswords"])
        self.assertIn("database", args.__dict__)
        args_short = pysswords.__main__.parse_args(["-D", "/tmp/pysswords"])
        self.assertIn("database", args_short.__dict__)

    @timethis
    def test_cli_parse_args_has_database_default_value(self):
        args = pysswords.__main__.parse_args([])
        self.assertEqual(args.database, pysswords.__main__.default_db())

    @timethis
    def test_cli_parse_args_has_add_arg(self):
        args = pysswords.__main__.parse_args(["--add"])
        self.assertIn("add", args.__dict__)
        args_short = pysswords.__main__.parse_args(["-a"])
        self.assertIn("add", args_short.__dict__)

    @timethis
    def test_cli_parse_args_add_arg_is_true_when_passed(self):
        args = pysswords.__main__.parse_args(["--add"])
        self.assertTrue(args.add)

    @timethis
    def test_cli_parse_args_has_remove_arg(self):
        credential_name = "example.com"
        args = pysswords.__main__.parse_args(["--remove", credential_name])
        args_short = pysswords.__main__.parse_args(["-r", credential_name])
        self.assertIn("remove", args.__dict__)
        self.assertIn("remove", args_short.__dict__)

    @timethis
    def test_cli_parse_args_remove_arg_has_credential_name_passed(self):
        credential_name = "example.com"
        args = pysswords.__main__.parse_args(["--remove", credential_name])
        self.assertTrue(args.remove, credential_name)

    @timethis
    def test_cli_parse_args_has_update_arg(self):
        credential_name = "example.com"
        args = pysswords.__main__.parse_args(["--update", credential_name])
        args_short = pysswords.__main__.parse_args(["-u", credential_name])
        self.assertIn("update", args.__dict__)
        self.assertIn("update", args_short.__dict__)

    @timethis
    def test_cli_parse_args_update_arg_has_credential_name_passed(self):
        credential_name = "example.com"
        args = pysswords.__main__.parse_args(["--update", credential_name])
        self.assertEqual(args.update, credential_name)

    @timethis
    def test_cli_parse_args_has_get_arg(self):
        credential_name = "example.com"
        args = pysswords.__main__.parse_args(["--get", credential_name])
        args_short = pysswords.__main__.parse_args(["-g", credential_name])
        self.assertIn("get", args.__dict__)
        self.assertIn("get", args_short.__dict__)

    @timethis
    def test_cli_parse_args_get_arg_has_credential_name_passed(self):
        credential_name = "example.com"
        args = pysswords.__main__.parse_args(["--get", credential_name])
        self.assertEqual(args.get, credential_name)

    @timethis
    def test_cli_parse_args_has_search_arg(self):
        credential_name = "example.com"
        args = pysswords.__main__.parse_args(["--search", credential_name])
        args_short = pysswords.__main__.parse_args(["-s", credential_name])
        self.assertIn("search", args.__dict__)
        self.assertIn("search", args_short.__dict__)

    @timethis
    def test_cli_parse_args_search_arg_has_credential_name_passed(self):
        credential_name = "example.com"
        args = pysswords.__main__.parse_args(["--search", credential_name])
        self.assertEqual(args.search, credential_name)

    @timethis
    def test_cli_raises_error_when_clipboard_passed_without_get_args(self):
        # pysswords.__main__.parse_args(["--clipboard"])
        with open(os.devnull, 'w') as devnull:
            with patch("sys.stderr", devnull):
                with self.assertRaises(SystemExit):
                    pysswords.__main__.parse_args(["--clipboard"])
            with patch("sys.stderr", devnull):
                with self.assertRaises(SystemExit):
                    pysswords.__main__.parse_args(["-c"])

    @timethis
    def test_cli_main_handles_with_init_arg_create_database(self):
        tempdb_path = os.path.join(self.tempdb_path, "temp")
        with patch("pysswords.__main__.Database") as mocked:
            with patch("pysswords.__main__.prompt"):
                pysswords.__main__.main(["-I", "-D", tempdb_path])
                self.assertTrue(mocked.create.called)

    @timethis
    def test_cli_main_add_credential_when_passed_add_arg(self):
        tmpdb = self.create_database()
        args = ["-D", tmpdb.path, "-a"]
        with patch("pysswords.__main__.Database.add") as mocked:
            with patch("pysswords.__main__.prompt") as mocked_prompt:
                mocked_prompt.side_effect = [
                    "example.com",
                    "doe",
                    "pass",
                    "No Comment"
                ]
                pysswords.__main__.main(args)
                self.assertIsInstance(mocked.call_args[0][0], Credential)

    @timethis
    def test_prompt_input_uses_default_arg(self):
        default = "123123123"
        with patch(BUILTINS_NAME + ".input") as mocked:
            __main__.prompt("Name", default)
            call_args, _ = mocked.call_args
            self.assertIn(default, call_args[0])

    @timethis
    def test_prompt_with_password_calls_prompt_password(self):
        with patch("pysswords.__main__.prompt_password") as mocked:
            pysswords.__main__.prompt("Pass:", password=True)
            self.assertTrue(mocked.called)

    @timethis
    def test_promt_password_returns_entered_password(self):
        with patch(BUILTINS_NAME + ".print"):
            with patch("pysswords.__main__.getpass") as mocked:
                entry = "entry"
                mocked.return_value = entry
                ret = pysswords.__main__.prompt_password("Pass:")
                self.assertEqual(entry, ret)

    @timethis
    def test_getpassphrase_raises_value_error_when_passwords_didnt_match(self):
        with patch(BUILTINS_NAME + ".print"):
            with patch("pysswords.__main__.getpass") as mocked:
                mocked.side_effect = ["pass", "wrong"] * 3
                with self.assertRaises(ValueError):
                    __main__.prompt_password("Password:")

    @timethis
    def test_get_credential_when_get_arg_passed(self):
        tempdb = self.create_database()
        credential_name = "example.com"
        args = ["-D", tempdb.path, "--get", credential_name]
        with patch("pysswords.db.Database.credential") as mocked:
            pysswords.__main__.main(args)
            self.assertTrue(mocked.called)
            mocked.assert_called_once_with(
                name=credential_name,
                login=None
            )

    @timethis
    def test_get_cedential_when_get_arg_passed_with_login(self):
        tempdb = self.create_database()
        credential = Credential("example.com", "doe", "_", "_")
        fullname = <EMAIL>(credential.<EMAIL>, credential.name)
        args = ["-D", tempdb.path, "--get", fullname]
        with patch("pysswords.db.Database.credential") as mocked:
            pysswords.__main__.main(args)
            self.assertTrue(mocked.called)
            mocked.assert_called_once_with(
                name=credential.name,
                login=credential.login
            )

    @timethis
    def test_split_name_returns_name_login_from_name(self):
        cred_name = "example.org"
        cred_login = "john.doe"
        credential_full_name = <EMAIL>(cred_login, cred_name)
        name, login = pysswords.__main__.split_name(credential_full_name)
        self.assertEqual(cred_name, name)
        self.assertEqual(cred_login, login)

    @timethis
    def test_split_name_returns_login_none_when_not_loginname_passed(self):
        cred_name = "@example.org"
        name, login = pysswords.__main__.split_name(cred_name)
        self.assertEqual(cred_name.strip("@"), name)
        self.assertEqual(None, login)

    @timethis
    def test_print_credentials(self):
        credentials = [
            some_credential()
        ]
        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
            pysswords.__main__.print_credentials(credentials)
            output = mock_stdout.getvalue()
        for credential in credentials:
            self.assertIn(credential.name, output)
            self.assertIn(credential.login, output)
            self.assertIn("***", output)
            self.assertIn(credential.comment, output)

    @timethis
    def test_print_plaintext_prints_passwords_plaintext(self):
        credentials = [
            some_credential()
        ]
        plaintext = "plaintext"<commit_msg>Test cli splitname raises value error when invalid name<commit_after>import argparse
import inspect
import os
import shutil
import sys
import unittest
import yaml
import time
from functools import wraps

try:
    from unittest.mock import patch, Mock
    from io import StringIO
except ImportError:
    # backwards compatbility with Python2
    from mock import patch, Mock
    from StringIO import StringIO

if sys.version_info >= (3,):
    BUILTINS_NAME = "builtins"
else:
    BUILTINS_NAME = "__builtin__"

import gnupg


__file__ = os.path.relpath(inspect.getsourcefile(lambda _: None))

TEST_DIR = os.path.join(os.path.dirname(os.path.relpath(__file__)))
TEST_DATA_DIR = os.path.join(TEST_DIR, "data")

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.relpath(__file__))))
import pysswords
from pysswords import __main__
from pysswords.db import Database, Credential

PROFILE = False


def timethis(func):
    ''' Decorator that reports the execution time.
    '''
    if PROFILE:
        @wraps(func)
        def wrapper(*args, **kwargs):
            start = time.time()
            result = func(*args, **kwargs)
            end = time.time()
            print("[{:.2f}]".format(end-start), func.__name__)
            return result
        return wrapper
    else:
        return func


def build_keys():
    gpg = gnupg.GPG(homedir="/tmp/pysswords")
    key_input = gpg.gen_key_input(
        name_real="Pysswords",
        name_email="<EMAIL>",
        name_comment="Auto-generated by Pysswords",
        key_length=512,
        expire_date=0,
        passphrase="<PASSWORD>"
    )
    key = gpg.gen_key(key_input)
    ascii_armored_public_keys = gpg.export_keys(key)
    ascii_armored_private_keys = gpg.export_keys(key, True)
    with open(os.path.join(TEST_DATA_DIR, "newkey.asc"), 'w') as f:
        f.write(ascii_armored_public_keys)
        f.write(ascii_armored_private_keys)


def mock_create_keyring(path, *args, **kwargs):
    """Import key.asc instead of generating new key
    passphrase used to create the key was '<PASSWORD>'"""
    keyring_path = os.path.join(path, ".keys")
    gpg = gnupg.GPG(homedir=keyring_path)
    with open(os.path.join(TEST_DATA_DIR, "key.asc")) as keyfile:
        gpg.import_keys(keyfile.read())
    return gpg.list_keys()[0]


def mock_gen_key(self, key_input):
    return mock_create_keyring(self.homedir)


def some_credential(**kwargs):
    return pysswords.db.Credential(
        name=kwargs.get("name", "example.com"),
        login=kwargs.get("login", "john.doe"),
        password=kwargs.get("password", "--BEGIN GPG-- X --END GPG--"),
        comment=kwargs.get("comment", "Some comments"),
    )


# @patch("pysswords.crypt.create_keyring", new=mock_create_keyring)
class CryptTests(unittest.TestCase):

    def setUp(self):
        self.path = os.path.join(TEST_DATA_DIR, "database")
        self.passphrase = "<PASSWORD>"
        self.cleanup()

    def tearDown(self):
        self.cleanup()

    def cleanup(self):
        if os.path.exists(self.path):
            shutil.rmtree(self.path)

    @timethis
    @patch("pysswords.crypt.create_keyring", new=mock_create_keyring)
    def test_create_keyring_adds_gpg_keys_to_path(self):
        pysswords.crypt.create_keyring(self.path, self.passphrase)
        pubring = os.path.join(self.path, ".keys", "pubring.gpg")
        secring = os.path.join(self.path, ".keys", "secring.gpg")
        self.assertTrue(os.path.isfile(pubring))
        self.assertTrue(os.path.isfile(secring))

    @timethis
    @patch("pysswords.crypt.create_keyring", new=mock_create_keyring)
    def test_create_keyring_adds_key_to_keyring(self):
        database = Database(self.path)
        pysswords.crypt.create_keyring(self.path, self.passphrase)
        gpg = gnupg.GPG(homedir=database.keys_path)
        self.assertEqual(1, len(gpg.list_keys()))

    @timethis
    @patch("pysswords.crypt.gnupg.GPG.gen_key", new=mock_gen_key)
    def test_generate_keys_return_valid_key(self):
        key = pysswords.crypt.generate_keys(self.path, self.passphrase)
        self.assertIsNotNone(key)
        self.assertEqual(key["fingerprint"],
                         '<KEY>')
    @timethis
    def test_generate_key_input_returns_batch_string_with_passphrase(self):
        batch = pysswords.crypt.generate_key_input(self.path, self.passphrase)
        self.assertIn("\nPassphrase: {}".format(self.passphrase), batch)

    @timethis
    def test_create_keyring_generate_keys(self):
        self.cleanup()
        with patch("pysswords.crypt.generate_keys") as mocked_generate:
            pysswords.crypt.create_keyring(self.path, self.passphrase)
            self.assertTrue(mocked_generate.called)


@patch("pysswords.db.database.create_keyring", new=mock_create_keyring)
class DatabaseTests(unittest.TestCase):

    __name__ = "DatabaseTests"

    def setUp(self):
        self.path = os.path.join(TEST_DATA_DIR, "database")
        self.passphrase = "<PASSWORD>"
        self.cleanup()

    def tearDown(self):
        self.cleanup()

    def cleanup(self):
        if os.path.exists(self.path):
            shutil.rmtree(self.path)
    @timethis
    def test_create_makedirs_at_path(self):
        test_path = os.path.join(self.path, "creation")
        if os.path.exists(self.path):
            shutil.rmtree(self.path)
        pysswords.db.Database.create(test_path, self.passphrase)
        self.assertTrue(os.path.exists(test_path))

    @timethis
    def test_create_keyring(self):
        database = Database.create(self.path, self.passphrase)
        self.assertIsInstance(database, pysswords.db.Database)
        self.assertTrue(len(database.gpg.list_keys()) == 1)

    @timethis
    def test_keys_path_returns_database_path_joined_with_dot_keys(self):
        database = Database.create(self.path, self.passphrase)
        keys_path = database.keys_path
        self.assertEqual(keys_path, os.path.join(self.path, ".keys"))

    @timethis
    def test_add_credential_make_dir_in_dbpath_with_credential_name(self):
        database = Database.create(self.path, self.passphrase)
        database.add(some_credential())
        credential_dir = os.path.join(self.path, some_credential().name)
        self.assertTrue(os.path.exists(credential_dir))
        self.assertTrue(os.path.isdir(credential_dir))

    @timethis
    def test_add_credential_createas_pyssword_file_named_after_login(self):
        database = Database.create(self.path, self.passphrase)
        credential = some_credential()
        database.add(credential)
        credential_dir = os.path.join(self.path, credential.name)
        credential_filename = "{}.pyssword".format(credential.login)
        credential_file = os.path.join(credential_dir, credential_filename)
        self.assertTrue(os.path.isfile(credential_file))
        with open(credential_file) as f:
            self.assertEqual(yaml.load(f.read()), credential)

    @timethis
    def test_add_credential_creates_dir_when_credential_name_is_a_dir(self):
        database = Database.create(self.path, self.passphrase)
        credential = some_credential(name="emails/misc/example.com")
        emails_dir = os.path.join(self.path, "emails")
        misc_dir = os.path.join(emails_dir, "misc")
        database.add(credential)
        self.assertTrue(os.path.isdir(emails_dir))
        self.assertTrue(os.path.isdir(misc_dir))

    @timethis
    def test_add_credential_returns_credential_path(self):
        database = Database.create(self.path, self.passphrase)
        credential = some_credential()
        credential_path = database.add(credential)
        expected_path = os.path.join(
            self.path,
            os.path.basename(credential.name),
            "{}.pyssword".format(credential.login)
        )
        self.assertEqual(credential_path, expected_path)

    @timethis
    def test_gpg_returns_valid_gnupg_gpg_object(self):
        database = Database.create(self.path, self.passphrase)
        gpg = database.gpg
        self.assertIsInstance(gpg, pysswords.db.database.gnupg.GPG)

    @timethis
    def test_credentials_returns_a_list_of_all_added_credentials(self):
        database = Database.create(self.path, self.passphrase)
        database.add(some_credential(name="example.com"))
        database.add(some_credential(name="archive.org"))
        credentials = database.credentials
        self.assertIsInstance(credentials, list)
        self.assertEqual(2, len(credentials))
        for credential in credentials:
            self.assertIsInstance(credential, pysswords.db.Credential)

    @timethis
    def test_add_repeated_credential_without_overwrite_on_raises_error(self):
        database = Database.create(self.path, self.passphrase)
        credential = some_credential()
        database.add(credential)
        with self.assertRaises(pysswords.db.CredentialExistsError):
            database.add(credential)

    @timethis
    def test_remove_deletes_pysswords_file(self):
        database = Database.create(self.path, self.passphrase)
        credential = some_credential()
        credential_path = pysswords.db.credential.expandpath(
            self.path,
            credential)
        database.add(credential)
        self.assertTrue(os.path.isfile(credential_path))
        database.remove(credential)
        self.assertFalse(os.path.isfile(credential_path))

    @timethis
    def test_remove_deletes_pyssword_dir_if_empty_after_deletion(self):
        database = Database.create(self.path, self.passphrase)
        credential = some_credential()
        credential_path = pysswords.db.credential.expandpath(
            self.path,
            credential)

        database.add(credential)
        self.assertTrue(os.path.exists(os.path.dirname(credential_path)))
        database.remove(credential)
        self.assertFalse(os.path.exists(os.path.dirname(credential_path)))

    @timethis
    def test_get_credential_by_name_returns_expected_credential(self):
        database = Database.create(self.path, self.passphrase)
        credential = some_credential(name="example.com")
        database.add(credential)
        found = database.credential(name=credential.name)

        self.assertIsInstance(found, list)
        self.assertTrue(all(True for c in found
                            if isinstance(c, pysswords.db.Credential)))
        self.assertTrue(any(True for c in found
                            if c == credential))

    @timethis
    def test_get_returns_unique_credential_when_login_is_passed(self):
        database = Database.create(self.path, self.passphrase)
        credential = some_credential(name="example.com")
        credential2 = some_credential(name="example.com", login="jonny.doe")
        database.add(credential)
        database.add(credential2)
        found = database.credential(name=credential.name,
                                    login=credential.login)
        self.assertEqual(found, [credential])

    @timethis
    def test_get_returns_no_element_when_name_not_found(self):
        database = Database.create(self.path, self.passphrase)
        credential = some_credential(name="example.com")
        database.add(credential)
        found = database.credential(name="not added")
        self.assertListEqual(found, [])

    @timethis
    def test_search_database_returns_list_with_matched_credentials(self):
        database = Database.create(self.path, self.passphrase)
        database.add(some_credential(name="example.com"))
        database.add(some_credential(name="github.com"))
        database.add(some_credential(name="twitter.com"))

        self.assertEqual(len(database.search("it")), 2)
        self.assertEqual(len(database.search("github")), 1)
        self.assertEqual(len(database.search("not there")), 0)

    @timethis
    def test_encrypt_text_returns_valid_encryption_ascii_gpg(self):
        database = Database.create(self.path, self.passphrase)
        text = "secret"
        encrypted = database.encrypt(text)
        self.assertIn("-BEGIN PGP MESSAGE-", encrypted)
        self.assertIn("-END PGP MESSAGE-", encrypted)

    @timethis
    def test_key_returns_expected_key_fingerprint(self):
        database = Database.create(self.path, self.passphrase)
        self.assertEqual(
            database.key(),
            "<KEY>")

    @timethis
    def test_key_returns_private_key_when_private_is_true(self):
        to_patch = "pysswords.db.database.gnupg.GPG.list_keys"
        with patch(to_patch) as mocked_list_keys:
            mocked_list_keys.return_value = [
                {"fingerprint": "<KEY>"}
            ]
            database = Database.create(self.path, self.passphrase)
            database.key(private=True)
            database.gpg.list_keys.assert_any_call_with(secret=True)
        self.assertEqual(
            database.key(private=True),
            "<KEY>")

    @timethis
    def test_decrypt_returns_plain_text_data(self):
        database = Database.create(self.path, self.passphrase)
        text = "secret"
        encrypted = database.encrypt(text)
        decrypted = database.decrypt(encrypted, passphrase=self.passphrase)
        self.assertEqual(decrypted, text)

    @timethis
    def test_update_credential_updates_credential_values(self):
        database = Database.create(self.path, self.passphrase)
        values = {
            "name": "example.com",
            "login": "jonh.doe",
            "password": "<PASSWORD>",
            "comment": "No Comments"
        }
        credential = some_credential(**values)
        database.add(credential)
        new_values = values
        new_values["login"] = "doe.john"
        database.update(credential, **new_values)
        found = database.credential(name=values["name"])[0]
        self.assertEqual(found._asdict(), new_values)


class CredentialTests(unittest.TestCase):

    def setUp(self):
        self.path = os.path.join(TEST_DATA_DIR, "database")
        self.cleanup()

    def tearDown(self):
        self.cleanup()

    def cleanup(self):
        if os.path.exists(self.path):
            shutil.rmtree(self.path)

    @timethis
    def test_credential_expandpath_returns_expected_path_to_credential(self):
        credential = some_credential()
        credential_path = pysswords.db.credential.expandpath(
            self.path,
            credential
        )
        expected_path = os.path.join(
            self.path,
            os.path.basename(credential.name),
            "{}.pyssword".format(credential.login)
        )
        self.assertEqual(credential_path, expected_path)

    @timethis
    def test_credential_content_returns_yaml_content_parseable_to_dict(self):
        content = pysswords.db.credential.content(some_credential())
        self.assertEqual(yaml.load(content), some_credential())


class UtilsTests(unittest.TestCase):

    @timethis
    def test_which_handle_windows_exe_extension_for_executables(self):
        with patch("pysswords.utils.os") as mocker:
            mocker.name = "nt"
            mocker.environ = {"PATH": "/"}
            mocker.pathsep = ":"
            mocked_join = Mock()
            mocker.path.join = mocked_join
            pysswords.utils.which("python")
            mocked_join.assert_any_call("/", "python.exe")


class ConsoleInterfaceTests(unittest.TestCase):

    def setUp(self):
        self.tempdb_path = os.path.join(TEST_DATA_DIR, "tmp")
        self.cleanup()
        self.passphrase = "<PASSWORD>"

    def tearDown(self):
        self.cleanup()

    def cleanup(self):
        if os.path.exists(self.tempdb_path):
            shutil.rmtree(self.tempdb_path)

    def create_database(self):
        with patch("pysswords.db.database.create_keyring",
                   new=mock_create_keyring):
            return Database.create(self.tempdb_path, self.passphrase)

    @timethis
    def test_cli_parse_args_returns_argparse_namespace(self):
        args = pysswords.__main__.parse_args(["--init"])
        self.assertIsInstance(args, argparse.Namespace)

    @timethis
    def test_cli_default_pyssword_dir(self):
        pysswords_dir = os.path.join(os.path.expanduser("~"), ".pysswords")
        self.assertEqual(pysswords_dir, pysswords.__main__.default_db())

    @timethis
    def test_cli_parse_args_has_init_arg(self):
        args = pysswords.__main__.parse_args(["--init"])
        self.assertIn("init", args.__dict__)
        args_short = pysswords.__main__.parse_args(["-I"])
        self.assertIn("init", args_short.__dict__)

    @timethis
    def test_cli_parse_args_has_database_arg(self):
        args = pysswords.__main__.parse_args(["--database", "/tmp/pysswords"])
        self.assertIn("database", args.__dict__)
        args_short = pysswords.__main__.parse_args(["-D", "/tmp/pysswords"])
        self.assertIn("database", args_short.__dict__)

    @timethis
    def test_cli_parse_args_has_database_default_value(self):
        args = pysswords.__main__.parse_args([])
        self.assertEqual(args.database, pysswords.__main__.default_db())

    @timethis
    def test_cli_parse_args_has_add_arg(self):
        args = pysswords.__main__.parse_args(["--add"])
        self.assertIn("add", args.__dict__)
        args_short = pysswords.__main__.parse_args(["-a"])
        self.assertIn("add", args_short.__dict__)

    @timethis
    def test_cli_parse_args_add_arg_is_true_when_passed(self):
        args = pysswords.__main__.parse_args(["--add"])
        self.assertTrue(args.add)

    @timethis
    def test_cli_parse_args_has_remove_arg(self):
        credential_name = "example.com"
        args = pysswords.__main__.parse_args(["--remove", credential_name])
        args_short = pysswords.__main__.parse_args(["-r", credential_name])
        self.assertIn("remove", args.__dict__)
        self.assertIn("remove", args_short.__dict__)

    @timethis
    def test_cli_parse_args_remove_arg_has_credential_name_passed(self):
        credential_name = "example.com"
        args = pysswords.__main__.parse_args(["--remove", credential_name])
        self.assertTrue(args.remove, credential_name)

    @timethis
    def test_cli_parse_args_has_update_arg(self):
        credential_name = "example.com"
        args = pysswords.__main__.parse_args(["--update", credential_name])
        args_short = pysswords.__main__.parse_args(["-u", credential_name])
        self.assertIn("update", args.__dict__)
        self.assertIn("update", args_short.__dict__)

    @timethis
    def test_cli_parse_args_update_arg_has_credential_name_passed(self):
        credential_name = "example.com"
        args = pysswords.__main__.parse_args(["--update", credential_name])
        self.assertEqual(args.update, credential_name)

    @timethis
    def test_cli_parse_args_has_get_arg(self):
        credential_name = "example.com"
        args = pysswords.__main__.parse_args(["--get", credential_name])
        args_short = pysswords.__main__.parse_args(["-g", credential_name])
        self.assertIn("get", args.__dict__)
        self.assertIn("get", args_short.__dict__)

    @timethis
    def test_cli_parse_args_get_arg_has_credential_name_passed(self):
        credential_name = "example.com"
        args = pysswords.__main__.parse_args(["--get", credential_name])
        self.assertEqual(args.get, credential_name)

    @timethis
    def test_cli_parse_args_has_search_arg(self):
        credential_name = "example.com"
        args = pysswords.__main__.parse_args(["--search", credential_name])
        args_short = pysswords.__main__.parse_args(["-s", credential_name])
        self.assertIn("search", args.__dict__)
        self.assertIn("search", args_short.__dict__)

    @timethis
    def test_cli_parse_args_search_arg_has_credential_name_passed(self):
        credential_name = "example.com"
        args = pysswords.__main__.parse_args(["--search", credential_name])
        self.assertEqual(args.search, credential_name)

    @timethis
    def test_cli_raises_error_when_clipboard_passed_without_get_args(self):
        # pysswords.__main__.parse_args(["--clipboard"])
        with open(os.devnull, 'w') as devnull:
            with patch("sys.stderr", devnull):
                with self.assertRaises(SystemExit):
                    pysswords.__main__.parse_args(["--clipboard"])
            with patch("sys.stderr", devnull):
                with self.assertRaises(SystemExit):
                    pysswords.__main__.parse_args(["-c"])

    @timethis
    def test_cli_main_handles_with_init_arg_create_database(self):
        tempdb_path = os.path.join(self.tempdb_path, "temp")
        with patch("pysswords.__main__.Database") as mocked:
            with patch("pysswords.__main__.prompt"):
                pysswords.__main__.main(["-I", "-D", tempdb_path])
                self.assertTrue(mocked.create.called)

    @timethis
    def test_cli_main_add_credential_when_passed_add_arg(self):
        tmpdb = self.create_database()
        args = ["-D", tmpdb.path, "-a"]
        with patch("pysswords.__main__.Database.add") as mocked:
            with patch("pysswords.__main__.prompt") as mocked_prompt:
                mocked_prompt.side_effect = [
                    "example.com",
                    "doe",
                    "pass",
                    "No Comment"
                ]
                pysswords.__main__.main(args)
                self.assertIsInstance(mocked.call_args[0][0], Credential)

    @timethis
    def test_prompt_input_uses_default_arg(self):
        default = "123123123"
        with patch(BUILTINS_NAME + ".input") as mocked:
            __main__.prompt("Name", default)
            call_args, _ = mocked.call_args
            self.assertIn(default, call_args[0])

    @timethis
    def test_prompt_with_password_calls_prompt_password(self):
        with patch("pysswords.__main__.prompt_password") as mocked:
            pysswords.__main__.prompt("Pass:", password=True)
            self.assertTrue(mocked.called)

    @timethis
    def test_promt_password_returns_entered_password(self):
        with patch(BUILTINS_NAME + ".print"):
            with patch("pysswords.__main__.getpass") as mocked:
                entry = "entry"
                mocked.return_value = entry
                ret = pysswords.__main__.prompt_password("Pass:")
                self.assertEqual(entry, ret)

    @timethis
    def test_getpassphrase_raises_value_error_when_passwords_didnt_match(self):
        with patch(BUILTINS_NAME + ".print"):
            with patch("pysswords.__main__.getpass") as mocked:
                mocked.side_effect = ["pass", "wrong"] * 3
                with self.assertRaises(ValueError):
                    __main__.prompt_password("Password:")

    @timethis
    def test_get_credential_when_get_arg_passed(self):
        tempdb = self.create_database()
        credential_name = "example.com"
        args = ["-D", tempdb.path, "--get", credential_name]
        with patch("pysswords.db.Database.credential") as mocked:
            pysswords.__main__.main(args)
            self.assertTrue(mocked.called)
            mocked.assert_called_once_with(
                name=credential_name,
                login=None
            )

    @timethis
    def test_get_cedential_when_get_arg_passed_with_login(self):
        tempdb = self.create_database()
        credential = Credential("example.com", "doe", "_", "_")
        fullname = <EMAIL>(credential.<EMAIL>, credential.name)
        args = ["-D", tempdb.path, "--get", fullname]
        with patch("pysswords.db.Database.credential") as mocked:
            pysswords.__main__.main(args)
            self.assertTrue(mocked.called)
            mocked.assert_called_once_with(
                name=credential.name,
                login=credential.login
            )

    @timethis
    def test_split_name_returns_name_login_from_name(self):
        cred_name = "example.org"
        cred_login = "john.doe"
        credential_full_name = "{}@{}".format(cred_login, cred_name)
        name, login = pysswords.__main__.split_name(credential_full_name)
        self.assertEqual(cred_name, name)
        self.assertEqual(cred_login, login)

    @timethis
    def test_split_name_raises_value_error_when_not_valid_name_given(self):
        invalid_name = ""
        with self.assertRaises(ValueError):
            pysswords.__main__.split_name(invalid_name)

    @timethis
    def test_split_name_returns_login_none_when_not_loginname_passed(self):
        cred_name = "@example.org"
        name, login = pysswords.__main__.split_name(cred_name)
        self.assertEqual(cred_name.strip("@"), name)
        self.assertEqual(None, login)

    @timethis
    def test_print_credentials(self):
        credentials = [
            some_credential()
        ]
        with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
            pysswords.__main__.print_credentials(credentials)
            output = mock_stdout.getvalue()
        for credential in credentials:
            self.assertIn(credential.name, output)
            self.assertIn(credential.login, output)
            self.assertIn("***", output)
            self.assertIn(credential.comment, output)

    @timethis
    def test_print_plaintext_prints_passwords_plaintext(self):
        credentials = [
            some_credential()
        ]
        plaintext = "plaintext"
{'fraction_non_alphanumeric': 0.11060017442321414, 'fraction_numerical': 0.00174423214144137, 'mean_word_length': 2.9275260781566246, 'pattern_counts': {'":': 14, '<': 31, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '452192', 'n_tokens_mistral': 16024, 'n_tokens_neox': 15813, 'n_words': 2249}