import os
import shlex
from contextlib import contextmanager

from nose.tools import *
from fudge import patch, Fake

from passta.testcases import PopenTestcase
from passta import gpg


class TestGpg(PopenTestcase):
    def test_gpg_spawns_gpg_with_correct_parameters_and_input(self):
        self.create_sh(
            'gpg', '(echo "$@"; /bin/cat) >{}/out.txt; echo output'.format(self.tmp_dir))
        eq_(gpg.gpg(['--foo'], b'input'), b'output\n')
        with open(os.path.join(self.tmp_dir, 'out.txt'), 'r') as f:
            eq_(shlex.split(f.readline()), gpg.GPG_OPTS + ['--foo'])
            eq_(f.read(), 'input')

    def test_gpg_raises_GpgError_when_gpg_is_not_installed(self):
        assert_raises(gpg.GpgError, gpg.gpg, [], 'input')

    def test_gpg_raises_GpgError_when_gpg_returns_nonzero(self):
        self.create_sh('gpg', 'exit 1'.format(self.tmp_dir))
        assert_raises(gpg.GpgError, gpg.gpg, [], b'input')

    @patch('passta.gpg.gpg')
    def test_encrypt_calls_gpg_with_appropriate_parameters(self, fake_gpg):
        (fake_gpg
            .expects_call()
            .with_args(gpg.ENCRYPT_OPTS, 'plain')
            .returns('encrypted'))
        eq_(gpg.encrypt('plain'), 'encrypted')

    @patch('passta.gpg.gpg')
    def test_decrypt_calls_gpg_with_appropriate_parameters(self, fake_gpg):
        (fake_gpg
            .expects_call()
            .with_args(gpg.DECRYPT_OPTS, 'encrypted')
            .returns('plain'))
        eq_(gpg.decrypt('encrypted'), 'plain')


class TestEncryptedFile:
    def setup(self):
        self.plaintext = Fake('plaintext')
        self.ciphertext = Fake('ciphertext')

    @contextmanager
    def patch_open(self, *args, **kwargs):
        fake_file = Fake('file')

        @contextmanager
        def _open(*args):
            yield fake_file

        with patch('builtins.open') as fake_open:
            fake_open.expects_call().with_args(*args, **kwargs).calls(_open)
            yield fake_file

    @patch('os.path.expanduser')
    def test_expands_the_filename(self, fake_expanduser):
        (fake_expanduser
            .expects_call()
            .with_args('abbreviated')
            .returns('expanded'))
        eq_(gpg.EncryptedFile('abbreviated').filename, 'expanded')

    @patch('passta.gpg.decrypt')
    def test_read_decodes_the_file_content(self, fake_decrypt):
        encrypted_file = gpg.EncryptedFile('/some-file')
        with self.patch_open('/some-file', 'rb') as fake_file:
            fake_file.expects('read').returns(self.ciphertext)
            (fake_decrypt
                .expects_call()
                .with_args(self.ciphertext)
                .returns(self.plaintext))
            (self.plaintext
                .expects('decode')
                .with_args(encrypted_file.ENCODING)
                .returns(self.plaintext))
            eq_(encrypted_file.read(), self.plaintext)

    @patch('passta.gpg.encrypt')
    def test_write_writes_encrypted_content_to_the_file(self, fake_encrypt):
        encrypted_file = gpg.EncryptedFile('/some-file')
        with self.patch_open('/some-file', 'wb') as fake_file:
            (self.plaintext
                .expects('encode')
                .with_args(encrypted_file.ENCODING)
                .returns(self.plaintext))
            (fake_encrypt
                .expects_call()
                .with_args(self.plaintext)
                .returns(self.ciphertext))
            fake_file.expects('write').with_args(self.ciphertext)
            encrypted_file.write(self.plaintext)
