import base64
import os.path
from shutil import copyfile
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.backends.openssl.rsa import _RSAPublicKey, _RSAPrivateKey
from asym_crypto_yaml import (decrypt_value, encrypt_value, Encrypted,
load_private_key_from_file, load_public_key_from_file,
generate_new_private_key, generate_new_public_key,
load, dump, NUMBER_OF_BYTES_PER_ENCRYPTED_CHUNK, KEY_CHUNK_SIZE,
SUPPORTED_KEY_SIZES, generate_private_key_to_file, generate_private_key_to_file, generate_public_key_to_file,
encrypt_value_and_print ,add_secret_to_yaml_file, decrypt_yaml_file_and_write_encrypted_file_to_disk,
reencrypt_secrets_and_write_to_yaml_file)
from functools import reduce


def test_serialization_and_deserialization_does_not_garble_output():
    """
    Ensure that encrypt/decrypt works even after serializing and deserializing
    """
    private_key = generate_new_private_key()
    public_key = generate_new_public_key(private_key)

    input_str = "AABB"

    encrypted_str = encrypt_value(input_str, public_key)

    built_dict  = {"sum_key": Encrypted(encrypted_str)}
    dump_output = dump(built_dict)

    str_to_load = dump(built_dict)
    load_output = load(str_to_load)

    dump_output = dump(load_output)

    load_output = load(dump_output)
    parsed_encrypted = load_output['sum_key']

    output_str = decrypt_value(parsed_encrypted, private_key)

    assert input_str == output_str

def test_load_public_key_from_file():
    """
    Test that we can still use an old public test key
    """
    public_key = load_public_key_from_file('fixtures/test.public')
    assert(isinstance(public_key, _RSAPublicKey))

def test_load_private_key_from_file():
    """
    Test that we can still use an private test key
    """
    private_key = load_private_key_from_file('fixtures/test.private')
    assert(isinstance(private_key, _RSAPrivateKey))

def test_generate_new_private_key():
    """
    Test that we can generate a new private key, and that it is in SUPPORTED_KEY_SIZES
    """
    private_key = generate_new_private_key()
    assert(isinstance(private_key, _RSAPrivateKey))

def test_generate_new_public_key():
    """
    Test that we can generate a new public key, and that it is in SUPPORTED_KEY_SIZES
    """
    private_key = generate_new_private_key()
    public_key = generate_new_public_key(private_key)
    assert(isinstance(public_key, _RSAPublicKey))

def test_encrypt_decrypt_for_short_values_with_no_chunking():
    """
    Test that we an encrypt and decrypt a short value
    """
    private_key = generate_new_private_key()
    public_key = generate_new_public_key(private_key)

    input_str = "A" * 2
    message = encrypt_value(input_str, public_key)
    output_str = decrypt_value(message, private_key)

    assert input_str == output_str


def test_supported_keys_and_generated_lengths_match_chunking_invariant():
    """
    Because of properties of the encryption algorithm we are using, there is a maxiumum
    number of bytes we can encryt. The maximum number of bytes is relative to the keysize used.
    For large values we break it into chunks, encrypt the chunks
    and append them into a single value and replace it in the yaml file.
    When we decrypt the value, we do the inverse.
    It is important that we test that the chunk size generated by encrypting with our supported key sizes
    is what we expect when new key sizes are added/removed so that encrypting long values will function.
    """
    for key_length in SUPPORTED_KEY_SIZES:
        private_key = generate_new_private_key(key_length)
        public_key = generate_new_public_key(private_key)

        public_key = load_public_key_from_file('fixtures/test.public')

        # Encrypted blob of various lengths should be the same number of bytes
        input_str = "你" * 2
        output1 = encrypt_value(input_str, public_key).encode('utf-8')


        input_str = "C" * KEY_CHUNK_SIZE
        output2 = encrypt_value(input_str, public_key).encode('utf-8')

        input_str = "!" * (KEY_CHUNK_SIZE + 1)
        output3 = encrypt_value(input_str, public_key).encode('utf-8')

        input_str = "z" * (KEY_CHUNK_SIZE * 10)
        output4 = encrypt_value(input_str, public_key).encode('utf-8')

        assert len(output1) == len(output2)
        assert len(output1) == NUMBER_OF_BYTES_PER_ENCRYPTED_CHUNK
        assert len(output3) == NUMBER_OF_BYTES_PER_ENCRYPTED_CHUNK * 2
        assert len(output4) == NUMBER_OF_BYTES_PER_ENCRYPTED_CHUNK * 10


def test_encrypt_decrypt_for_long_values_with_chunking():
    """
    Test that for a value that is too large to be encrypted in one pass
    that we can encrypt/decrypt it.
    """
    private_key = generate_new_private_key()
    public_key = generate_new_public_key(private_key)
    input_str = "你" * KEY_CHUNK_SIZE * 10

    message = encrypt_value(input_str, public_key)
    output_str = decrypt_value(message, private_key)
    assert input_str == output_str

def test_substitution_for_pyyaml():
    """
    One thing we wanted to capture in the design of this library is that
    It should work as a passthrough to pyyaml.load, verify that 
    non dictionary inputs have the same output as pyyaml.load, and that they function at all.
    It should also work with encrypted values, even if no key is passed
    """
    assert load('some str') == 'some str'
    with open('fixtures/test_substitution_for_pyyaml.yml') as f:
        loaded_dict = load(f)
        assert isinstance(loaded_dict['PASSWORD'], Encrypted)

def test_generate_private_key_to_file():
    output_filename = "test_output/test_generate_private_key_to_file.private"
    generate_private_key_to_file(output_filename)
    assert os.path.isfile(output_filename) 
    loaded_private_key = load_private_key_from_file(output_filename)
    assert(isinstance(loaded_private_key, _RSAPrivateKey))

def test_generate_public_key_to_file():
    private_key_output_filename = "test_output/test_generate_public_key_to_file.private"
    public_key_output_filename = "test_output/test_generate_public_key_to_file.public"
    generate_private_key_to_file(private_key_output_filename)
    generate_public_key_to_file(private_key_output_filename, public_key_output_filename)
    assert os.path.isfile(private_key_output_filename)
    assert os.path.isfile(public_key_output_filename)

    loaded_public_key = load_public_key_from_file(public_key_output_filename)
    assert(isinstance(loaded_public_key, _RSAPublicKey))

def test_encrypt_value_and_print():
    private_key_output_filename = "test_output/test_encrypt_value_and_print.private"
    public_key_output_filename = "test_output/test_encrypt_value_and_print.public"
    private_key = generate_private_key_to_file(private_key_output_filename)
    public_key = generate_public_key_to_file(private_key_output_filename, public_key_output_filename)

    input_str = "A" * 2
    message = encrypt_value_and_print(input_str, public_key_output_filename)
    output_str = decrypt_value(message, private_key)

    assert input_str == output_str

def test_add_secret_to_yaml_file():
    private_key_output_filename = "test_output/test_add_secret_to_yaml_file.private"
    public_key_output_filename = "test_output/test_add_secret_to_yaml_file.public"
    private_key = generate_private_key_to_file(private_key_output_filename)
    public_key = generate_public_key_to_file(private_key_output_filename, public_key_output_filename)
    test_key_value = "TEST_KEY"

    yaml_file_fixture = "fixtures/test_add_secret_to_yaml_file.yml"
    yaml_file_to_append_to = "test_output/test_add_secret_to_yaml_file.yml"
    copyfile(yaml_file_fixture, yaml_file_to_append_to)
    
    before_dict = None
    with open(yaml_file_to_append_to) as f:
        before_dict = load(f)

    # Check for expected test data
    assert before_dict["Y"] == "B"
    assert before_dict["X"] == "B"

    # Check the value we are adding is not already there
    assert test_key_value not in before_dict
    add_secret_to_yaml_file(test_key_value, test_key_value, public_key_output_filename, yaml_file_to_append_to)

    with open(yaml_file_to_append_to) as f:
        after_dict = load(f)
    # Test the value was added
    assert test_key_value in after_dict

    # Test the value is encrypted.
    assert isinstance(after_dict[test_key_value], Encrypted)

    # Test the value was actually encrypted
    assert after_dict[test_key_value] != test_key_value

    # Test the expected test data was not modified, and is still present.
    assert after_dict["Y"] == "B"
    assert after_dict["X"] == "B"

    before_keys = list(before_dict.keys())
    after_keys  = list(after_dict.keys())

    # Zip stops when the shorter of the two stops
    # so these should always be equal as the new key should
    # have been added to the end, this checks
    # that order was preserved.
    for before_key, after_key in zip(before_keys, after_keys):
        assert before_key == after_key

def test_decrypt_yaml_file_and_write_encrypted_file_to_disk():
    private_key_output_filename = "test_output/test_decrypt_yaml_file_and_write_encrypted_file_to_disk.private"
    public_key_output_filename = "test_output/test_decrypt_yaml_file_and_write_encrypted_file_to_disk.public"
    private_key = generate_private_key_to_file(private_key_output_filename)
    public_key = generate_public_key_to_file(private_key_output_filename, public_key_output_filename)
    test_key_value = "C"

    yaml_file_fixture = "fixtures/test_decrypt_yaml_file_and_write_encrypted_file_to_disk.yml"
    yaml_file_to_append_to = "test_output/test_decrypt_yaml_file_and_write_encrypted_file_to_disk.yml"
    copyfile(yaml_file_fixture, yaml_file_to_append_to)

    add_secret_to_yaml_file(test_key_value, test_key_value, public_key_output_filename, yaml_file_to_append_to)
   
    after_dict = None
    with open(yaml_file_to_append_to) as f:
        after_dict = load(f, private_key_output_filename)
    
    after_dict["A"] = "A"
    after_dict["B"] = "B"
    after_dict["C"] = "C"

def test_reencrypt_secrets_and_write_to_yaml_file():

    private_key_file = "fixtures/test.private"
    private_key = load_private_key_from_file(private_key_file)
    
    yaml_file_fixture = "fixtures/test_reencrypt_secrets_and_write_to_yaml_file.yml"
    input_yaml_file = "test_output/test_reencrypt_secrets_and_write_to_yaml_file.yml"
    test_encrypted_key = "C"

    copyfile(yaml_file_fixture, input_yaml_file)

    private_key_output_filename_new = "test_output/test_new_reencrypt_secrets_and_write_to_yaml_file.private"
    public_key_output_filename_new = "test_output/test_new_reencrypt_secrets_and_write_to_yaml_file.public"
    private_key_new = generate_private_key_to_file(private_key_output_filename_new)
    public_key_new = generate_public_key_to_file(private_key_output_filename_new, public_key_output_filename_new)

    reencrypt_secrets_and_write_to_yaml_file(input_yaml_file, private_key_file, public_key_output_filename_new)
   
    before_dict = None
    with open(yaml_file_fixture) as f:
        before_dict = load(f)
   
    # Check for expected test data
    assert before_dict["A"] == "A"
    assert before_dict["B"] == "B"

    # Check for expected encrypted test data
    test_encrypted_key_value = decrypt_value(before_dict[test_encrypted_key], private_key)
    assert test_encrypted_key_value == "C"

    after_dict = None
    with open(input_yaml_file) as f:
        after_dict = load(f)

    # Test the expected test data was not modified, and is still present
    assert after_dict["A"] == "A"
    assert after_dict["B"] == "B"

    # Test the value is re-encrypted 
    test_encrypted_key_value = decrypt_value(after_dict[test_encrypted_key], private_key_new)
    assert test_encrypted_key_value == "C"


def test_add_nested_secret_to_yaml_file():
    private_key_output_filename = "test_output/test_add_nested_secret_to_yaml_file.private"
    public_key_output_filename = "test_output/test_add_nested_secret_to_yaml_file.public"
    private_key = generate_private_key_to_file(private_key_output_filename)
    public_key = generate_public_key_to_file(private_key_output_filename, public_key_output_filename)
    nested_value = "test"
    nested_key = "TEST_PARENT_KEY:TEST_CHILD_KEY"

    yaml_file_fixture = "fixtures/test_add_nested_secret_to_yaml_file.yml"
    yaml_file_to_append_to = "test_output/test_add_nested_secret_to_yaml_file.yml"
    copyfile(yaml_file_fixture, yaml_file_to_append_to)

    before_dict = None
    with open(yaml_file_to_append_to) as f:
        before_dict = load(f)

    # Check for expected test data
    assert before_dict["Y"] == "B"
    assert before_dict["X"] == "B"

    add_secret_to_yaml_file(nested_key, nested_value, public_key_output_filename, yaml_file_to_append_to)

    with open(yaml_file_to_append_to) as f:
        after_dict = load(f)

    # Test the value is encrypted.
    assert isinstance(reduce(lambda x, y: x[y], nested_key.split(":"), after_dict), Encrypted)

    # Test the value was actually encrypted
    assert reduce(lambda x, y: x[y], nested_key.split(":"), after_dict) != nested_value

    # Test the expected test data was not modified, and is still present.
    assert after_dict["Y"] == "B"
    assert after_dict["X"] == "B"

    before_keys = list(before_dict.keys())
    after_keys = list(after_dict.keys())

    # Zip stops when the shorter of the two stops
    # so these should always be equal as the new key should
    # have been added to the end, this checks
    # that order was preserved.
    for before_key, after_key in zip(before_keys, after_keys):
        assert before_key == after_key

import base64
import os.path
from shutil import copyfile
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.backends.openssl.rsa import _RSAPublicKey, _RSAPrivateKey
from asym_crypto_yaml import (decrypt_value, encrypt_value, Encrypted,
load_private_key_from_file, load_public_key_from_file,
generate_new_private_key, generate_new_public_key,
load, dump, NUMBER_OF_BYTES_PER_ENCRYPTED_CHUNK, KEY_CHUNK_SIZE,
SUPPORTED_KEY_SIZES, generate_private_key_to_file, generate_private_key_to_file, generate_public_key_to_file,
encrypt_value_and_print ,add_secret_to_yaml_file, decrypt_yaml_file_and_write_encrypted_file_to_disk,
reencrypt_secrets_and_write_to_yaml_file)
from functools import reduce


def test_serialization_and_deserialization_does_not_garble_output():
    """
    Ensure that encrypt/decrypt works even after serializing and deserializing
    """
    private_key = generate_new_private_key()
    public_key = generate_new_public_key(private_key)

    input_str = "AABB"

    encrypted_str = encrypt_value(input_str, public_key)

    built_dict  = {"sum_key": Encrypted(encrypted_str)}
    dump_output = dump(built_dict)

    str_to_load = dump(built_dict)
    load_output = load(str_to_load)

    dump_output = dump(load_output)

    load_output = load(dump_output)
    parsed_encrypted = load_output['sum_key']

    output_str = decrypt_value(parsed_encrypted, private_key)

    assert input_str == output_str

def test_load_public_key_from_file():
    """
    Test that we can still use an old public test key
    """
    public_key = load_public_key_from_file('fixtures/test.public')
    assert(isinstance(public_key, _RSAPublicKey))

def test_load_private_key_from_file():
    """
    Test that we can still use an private test key
    """
    private_key = load_private_key_from_file('fixtures/test.private')
    assert(isinstance(private_key, _RSAPrivateKey))

def test_generate_new_private_key():
    """
    Test that we can generate a new private key, and that it is in SUPPORTED_KEY_SIZES
    """
    private_key = generate_new_private_key()
    assert(isinstance(private_key, _RSAPrivateKey))

def test_generate_new_public_key():
    """
    Test that we can generate a new public key, and that it is in SUPPORTED_KEY_SIZES
    """
    private_key = generate_new_private_key()
    public_key = generate_new_public_key(private_key)
    assert(isinstance(public_key, _RSAPublicKey))

def test_encrypt_decrypt_for_short_values_with_no_chunking():
    """
    Test that we an encrypt and decrypt a short value
    """
    private_key = generate_new_private_key()
    public_key = generate_new_public_key(private_key)

    input_str = "A" * 2
    message = encrypt_value(input_str, public_key)
    output_str = decrypt_value(message, private_key)

    assert input_str == output_str


def test_supported_keys_and_generated_lengths_match_chunking_invariant():
    """
    Because of properties of the encryption algorithm we are using, there is a maxiumum
    number of bytes we can encryt. The maximum number of bytes is relative to the keysize used.
    For large values we break it into chunks, encrypt the chunks
    and append them into a single value and replace it in the yaml file.
    When we decrypt the value, we do the inverse.
    It is important that we test that the chunk size generated by encrypting with our supported key sizes
    is what we expect when new key sizes are added/removed so that encrypting long values will function.
    """
    for key_length in SUPPORTED_KEY_SIZES:
        private_key = generate_new_private_key(key_length)
        public_key = generate_new_public_key(private_key)

        public_key = load_public_key_from_file('fixtures/test.public')

        # Encrypted blob of various lengths should be the same number of bytes
        input_str = "你" * 2
        output1 = encrypt_value(input_str, public_key).encode('utf-8')


        input_str = "C" * KEY_CHUNK_SIZE
        output2 = encrypt_value(input_str, public_key).encode('utf-8')

        input_str = "!" * (KEY_CHUNK_SIZE + 1)
        output3 = encrypt_value(input_str, public_key).encode('utf-8')

        input_str = "z" * (KEY_CHUNK_SIZE * 10)
        output4 = encrypt_value(input_str, public_key).encode('utf-8')

        assert len(output1) == len(output2)
        assert len(output1) == NUMBER_OF_BYTES_PER_ENCRYPTED_CHUNK
        assert len(output3) == NUMBER_OF_BYTES_PER_ENCRYPTED_CHUNK * 2
        assert len(output4) == NUMBER_OF_BYTES_PER_ENCRYPTED_CHUNK * 10


def test_encrypt_decrypt_for_long_values_with_chunking():
    """
    Test that for a value that is too large to be encrypted in one pass
    that we can encrypt/decrypt it.
    """
    private_key = generate_new_private_key()
    public_key = generate_new_public_key(private_key)
    input_str = "你" * KEY_CHUNK_SIZE * 10

    message = encrypt_value(input_str, public_key)
    output_str = decrypt_value(message, private_key)
    assert input_str == output_str

def test_substitution_for_pyyaml():
    """
    One thing we wanted to capture in the design of this library is that
    It should work as a passthrough to pyyaml.load, verify that 
    non dictionary inputs have the same output as pyyaml.load, and that they function at all.
    It should also work with encrypted values, even if no key is passed
    """
    assert load('some str') == 'some str'
    with open('fixtures/test_substitution_for_pyyaml.yml') as f:
        loaded_dict = load(f)
        assert isinstance(loaded_dict['PASSWORD'], Encrypted)

def test_generate_private_key_to_file():
    output_filename = "test_output/test_generate_private_key_to_file.private"
    generate_private_key_to_file(output_filename)
    assert os.path.isfile(output_filename) 
    loaded_private_key = load_private_key_from_file(output_filename)
    assert(isinstance(loaded_private_key, _RSAPrivateKey))

def test_generate_public_key_to_file():
    private_key_output_filename = "test_output/test_generate_public_key_to_file.private"
    public_key_output_filename = "test_output/test_generate_public_key_to_file.public"
    generate_private_key_to_file(private_key_output_filename)
    generate_public_key_to_file(private_key_output_filename, public_key_output_filename)
    assert os.path.isfile(private_key_output_filename)
    assert os.path.isfile(public_key_output_filename)

    loaded_public_key = load_public_key_from_file(public_key_output_filename)
    assert(isinstance(loaded_public_key, _RSAPublicKey))

def test_encrypt_value_and_print():
    private_key_output_filename = "test_output/test_encrypt_value_and_print.private"
    public_key_output_filename = "test_output/test_encrypt_value_and_print.public"
    private_key = generate_private_key_to_file(private_key_output_filename)
    public_key = generate_public_key_to_file(private_key_output_filename, public_key_output_filename)

    input_str = "A" * 2
    message = encrypt_value_and_print(input_str, public_key_output_filename)
    output_str = decrypt_value(message, private_key)

    assert input_str == output_str

def test_add_secret_to_yaml_file():
    private_key_output_filename = "test_output/test_add_secret_to_yaml_file.private"
    public_key_output_filename = "test_output/test_add_secret_to_yaml_file.public"
    private_key = generate_private_key_to_file(private_key_output_filename)
    public_key = generate_public_key_to_file(private_key_output_filename, public_key_output_filename)
    test_key_value = "TEST_KEY"

    yaml_file_fixture = "fixtures/test_add_secret_to_yaml_file.yml"
    yaml_file_to_append_to = "test_output/test_add_secret_to_yaml_file.yml"
    copyfile(yaml_file_fixture, yaml_file_to_append_to)
    
    before_dict = None
    with open(yaml_file_to_append_to) as f:
        before_dict = load(f)

    # Check for expected test data
    assert before_dict["Y"] == "B"
    assert before_dict["X"] == "B"

    # Check the value we are adding is not already there
    assert test_key_value not in before_dict
    add_secret_to_yaml_file(test_key_value, test_key_value, public_key_output_filename, yaml_file_to_append_to)

    with open(yaml_file_to_append_to) as f:
        after_dict = load(f)
    # Test the value was added
    assert test_key_value in after_dict

    # Test the value is encrypted.
    assert isinstance(after_dict[test_key_value], Encrypted)

    # Test the value was actually encrypted
    assert after_dict[test_key_value] != test_key_value

    # Test the expected test data was not modified, and is still present.
    assert after_dict["Y"] == "B"
    assert after_dict["X"] == "B"

    before_keys = list(before_dict.keys())
    after_keys  = list(after_dict.keys())

    # Zip stops when the shorter of the two stops
    # so these should always be equal as the new key should
    # have been added to the end, this checks
    # that order was preserved.
    for before_key, after_key in zip(before_keys, after_keys):
        assert before_key == after_key

def test_decrypt_yaml_file_and_write_encrypted_file_to_disk():
    private_key_output_filename = "test_output/test_decrypt_yaml_file_and_write_encrypted_file_to_disk.private"
    public_key_output_filename = "test_output/test_decrypt_yaml_file_and_write_encrypted_file_to_disk.public"
    private_key = generate_private_key_to_file(private_key_output_filename)
    public_key = generate_public_key_to_file(private_key_output_filename, public_key_output_filename)
    test_key_value = "C"

    yaml_file_fixture = "fixtures/test_decrypt_yaml_file_and_write_encrypted_file_to_disk.yml"
    yaml_file_to_append_to = "test_output/test_decrypt_yaml_file_and_write_encrypted_file_to_disk.yml"
    copyfile(yaml_file_fixture, yaml_file_to_append_to)

    add_secret_to_yaml_file(test_key_value, test_key_value, public_key_output_filename, yaml_file_to_append_to)
   
    after_dict = None
    with open(yaml_file_to_append_to) as f:
        after_dict = load(f, private_key_output_filename)
    
    after_dict["A"] = "A"
    after_dict["B"] = "B"
    after_dict["C"] = "C"

def test_reencrypt_secrets_and_write_to_yaml_file():

    private_key_file = "fixtures/test.private"
    private_key = load_private_key_from_file(private_key_file)
    
    yaml_file_fixture = "fixtures/test_reencrypt_secrets_and_write_to_yaml_file.yml"
    input_yaml_file = "test_output/test_reencrypt_secrets_and_write_to_yaml_file.yml"
    test_encrypted_key = "C"

    copyfile(yaml_file_fixture, input_yaml_file)

    private_key_output_filename_new = "test_output/test_new_reencrypt_secrets_and_write_to_yaml_file.private"
    public_key_output_filename_new = "test_output/test_new_reencrypt_secrets_and_write_to_yaml_file.public"
    private_key_new = generate_private_key_to_file(private_key_output_filename_new)
    public_key_new = generate_public_key_to_file(private_key_output_filename_new, public_key_output_filename_new)

    reencrypt_secrets_and_write_to_yaml_file(input_yaml_file, private_key_file, public_key_output_filename_new)
   
    before_dict = None
    with open(yaml_file_fixture) as f:
        before_dict = load(f)
   
    # Check for expected test data
    assert before_dict["A"] == "A"
    assert before_dict["B"] == "B"

    # Check for expected encrypted test data
    test_encrypted_key_value = decrypt_value(before_dict[test_encrypted_key], private_key)
    assert test_encrypted_key_value == "C"

    after_dict = None
    with open(input_yaml_file) as f:
        after_dict = load(f)

    # Test the expected test data was not modified, and is still present
    assert after_dict["A"] == "A"
    assert after_dict["B"] == "B"

    # Test the value is re-encrypted 
    test_encrypted_key_value = decrypt_value(after_dict[test_encrypted_key], private_key_new)
    assert test_encrypted_key_value == "C"


def test_add_nested_secret_to_yaml_file():
    private_key_output_filename = "test_output/test_add_nested_secret_to_yaml_file.private"
    public_key_output_filename = "test_output/test_add_nested_secret_to_yaml_file.public"
    private_key = generate_private_key_to_file(private_key_output_filename)
    public_key = generate_public_key_to_file(private_key_output_filename, public_key_output_filename)
    nested_value = "test"
    nested_key = "TEST_PARENT_KEY:TEST_CHILD_KEY"

    yaml_file_fixture = "fixtures/test_add_nested_secret_to_yaml_file.yml"
    yaml_file_to_append_to = "test_output/test_add_nested_secret_to_yaml_file.yml"
    copyfile(yaml_file_fixture, yaml_file_to_append_to)

    before_dict = None
    with open(yaml_file_to_append_to) as f:
        before_dict = load(f)

    # Check for expected test data
    assert before_dict["Y"] == "B"
    assert before_dict["X"] == "B"

    add_secret_to_yaml_file(nested_key, nested_value, public_key_output_filename, yaml_file_to_append_to)

    with open(yaml_file_to_append_to) as f:
        after_dict = load(f)

    # Test the value is encrypted.
    assert isinstance(reduce(lambda x, y: x[y], nested_key.split(":"), after_dict), Encrypted)

    # Test the value was actually encrypted
    assert reduce(lambda x, y: x[y], nested_key.split(":"), after_dict) != nested_value

    # Test the expected test data was not modified, and is still present.
    assert after_dict["Y"] == "B"
    assert after_dict["X"] == "B"

    before_keys = list(before_dict.keys())
    after_keys = list(after_dict.keys())

    # Zip stops when the shorter of the two stops
    # so these should always be equal as the new key should
    # have been added to the end, this checks
    # that order was preserved.
    for before_key, after_key in zip(before_keys, after_keys):
        assert before_key == after_key

Ensure that encrypt/decrypt works even after serializing and deserializing Test that we can still use an old public test key Test that we can still use an private test key Test that we can generate a new private key, and that it is in SUPPORTED_KEY_SIZES Test that we can generate a new public key, and that it is in SUPPORTED_KEY_SIZES Test that we an encrypt and decrypt a short value Because of properties of the encryption algorithm we are using, there is a maxiumum     number of bytes we can encryt. The maximum number of bytes is relative to the keysize used.     For large values we break it into chunks, encrypt the chunks     and append them into a single value and replace it in the yaml file.     When we decrypt the value, we do the inverse.     It is important that we test that the chunk size generated by encrypting with our supported key sizes     is what we expect when new key sizes are added/removed so that encrypting long values will function. # Encrypted blob of various lengths should be the same number of bytes Test that for a value that is too large to be encrypted in one pass     that we can encrypt/decrypt it. One thing we wanted to capture in the design of this library is that     It should work as a passthrough to pyyaml.load, verify that      non dictionary inputs have the same output as pyyaml.load, and that they function at all.     It should also work with encrypted values, even if no key is passed # Check for expected test data # Check the value we are adding is not already there # Test the value was added # Test the value is encrypted. # Test the value was actually encrypted # Test the expected test data was not modified, and is still present. # Zip stops when the shorter of the two stops # so these should always be equal as the new key should # have been added to the end, this checks # that order was preserved. # Check for expected test data # Check for expected encrypted test data # Test the expected test data was not modified, and is still present # Test the value is re-encrypted # Check for expected test data # Test the value is encrypted. # Test the value was actually encrypted # Test the expected test data was not modified, and is still present. # Zip stops when the shorter of the two stops # so these should always be equal as the new key should # have been added to the end, this checks # that order was preserved.