# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import boto3

from moto import mock_secretsmanager, mock_lambda, settings
from moto.core import ACCOUNT_ID
from botocore.exceptions import ClientError
import string
import pytz
from datetime import datetime
import sure  # noqa
import pytest
from six import b

DEFAULT_SECRET_NAME = "test-secret"


@mock_secretsmanager
def test_get_secret_value():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    create_secret = conn.create_secret(
        Name="java-util-test-password", SecretString="foosecret"
    )
    result = conn.get_secret_value(SecretId="java-util-test-password")
    assert result["SecretString"] == "foosecret"


@mock_secretsmanager
def test_get_secret_value_by_arn():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    name = "java-util-test-password"
    secret_value = "test_get_secret_value_by_arn"
    result = conn.create_secret(Name=name, SecretString=secret_value)
    arn = result["ARN"]
    arn.should.match(
        "^arn:aws:secretsmanager:us-west-2:{}:secret:{}".format(ACCOUNT_ID, name)
    )

    result = conn.get_secret_value(SecretId=arn)
    assert result["SecretString"] == secret_value


@mock_secretsmanager
def test_get_secret_value_binary():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    create_secret = conn.create_secret(
        Name="java-util-test-password", SecretBinary=b("foosecret")
    )
    result = conn.get_secret_value(SecretId="java-util-test-password")
    assert result["SecretBinary"] == b("foosecret")


@mock_secretsmanager
def test_get_secret_that_does_not_exist():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    with pytest.raises(ClientError) as cm:
        result = conn.get_secret_value(SecretId="i-dont-exist")

    assert (
        "Secrets Manager can't find the specified secret."
        == cm.value.response["Error"]["Message"]
    )


@mock_secretsmanager
def test_get_secret_that_does_not_match():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    create_secret = conn.create_secret(
        Name="java-util-test-password", SecretString="foosecret"
    )

    with pytest.raises(ClientError) as cm:
        result = conn.get_secret_value(SecretId="i-dont-match")

    assert (
        "Secrets Manager can't find the specified secret."
        == cm.value.response["Error"]["Message"]
    )


@mock_secretsmanager
def test_get_secret_value_that_is_marked_deleted():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")

    conn.delete_secret(SecretId="test-secret")

    with pytest.raises(ClientError):
        result = conn.get_secret_value(SecretId="test-secret")


@mock_secretsmanager
def test_get_secret_that_has_no_value():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    create_secret = conn.create_secret(Name="java-util-test-password")

    with pytest.raises(ClientError) as cm:
        result = conn.get_secret_value(SecretId="java-util-test-password")

    assert (
        "Secrets Manager can't find the specified secret value for staging label: AWSCURRENT"
        == cm.value.response["Error"]["Message"]
    )


@mock_secretsmanager
def test_get_secret_version_that_does_not_exist():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    result = conn.create_secret(Name="java-util-test-password")
    secret_arn = result["ARN"]
    missing_version_id = "00000000-0000-0000-0000-000000000000"

    with pytest.raises(ClientError) as cm:
        conn.get_secret_value(SecretId=secret_arn, VersionId=missing_version_id)

    assert (
        "An error occurred (ResourceNotFoundException) when calling the GetSecretValue operation: Secrets "
        "Manager can't find the specified secret value for VersionId: 00000000-0000-0000-0000-000000000000"
    ) == cm.value.response["Error"]["Message"]


@mock_secretsmanager
def test_create_secret():
    conn = boto3.client("secretsmanager", region_name="us-east-1")

    result = conn.create_secret(Name="test-secret", SecretString="foosecret")
    assert result["ARN"]
    assert result["Name"] == "test-secret"
    secret = conn.get_secret_value(SecretId="test-secret")
    assert secret["SecretString"] == "foosecret"


@mock_secretsmanager
def test_create_secret_with_tags():
    conn = boto3.client("secretsmanager", region_name="us-east-1")
    secret_name = "test-secret-with-tags"

    result = conn.create_secret(
        Name=secret_name,
        SecretString="foosecret",
        Tags=[{"Key": "Foo", "Value": "Bar"}, {"Key": "Mykey", "Value": "Myvalue"}],
    )
    assert result["ARN"]
    assert result["Name"] == secret_name
    secret_value = conn.get_secret_value(SecretId=secret_name)
    assert secret_value["SecretString"] == "foosecret"
    secret_details = conn.describe_secret(SecretId=secret_name)
    assert secret_details["Tags"] == [
        {"Key": "Foo", "Value": "Bar"},
        {"Key": "Mykey", "Value": "Myvalue"},
    ]


@mock_secretsmanager
def test_create_secret_with_description():
    conn = boto3.client("secretsmanager", region_name="us-east-1")
    secret_name = "test-secret-with-tags"

    result = conn.create_secret(
        Name=secret_name, SecretString="foosecret", Description="desc"
    )
    assert result["ARN"]
    assert result["Name"] == secret_name
    secret_value = conn.get_secret_value(SecretId=secret_name)
    assert secret_value["SecretString"] == "foosecret"
    secret_details = conn.describe_secret(SecretId=secret_name)
    assert secret_details["Description"] == "desc"


@mock_secretsmanager
def test_create_secret_with_tags_and_description():
    conn = boto3.client("secretsmanager", region_name="us-east-1")
    secret_name = "test-secret-with-tags"

    result = conn.create_secret(
        Name=secret_name,
        SecretString="foosecret",
        Description="desc",
        Tags=[{"Key": "Foo", "Value": "Bar"}, {"Key": "Mykey", "Value": "Myvalue"}],
    )
    assert result["ARN"]
    assert result["Name"] == secret_name
    secret_value = conn.get_secret_value(SecretId=secret_name)
    assert secret_value["SecretString"] == "foosecret"
    secret_details = conn.describe_secret(SecretId=secret_name)
    assert secret_details["Tags"] == [
        {"Key": "Foo", "Value": "Bar"},
        {"Key": "Mykey", "Value": "Myvalue"},
    ]
    assert secret_details["Description"] == "desc"


@mock_secretsmanager
def test_delete_secret():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")

    deleted_secret = conn.delete_secret(SecretId="test-secret")

    assert deleted_secret["ARN"]
    assert deleted_secret["Name"] == "test-secret"
    assert deleted_secret["DeletionDate"] > datetime.fromtimestamp(1, pytz.utc)

    secret_details = conn.describe_secret(SecretId="test-secret")

    assert secret_details["ARN"]
    assert secret_details["Name"] == "test-secret"
    assert secret_details["DeletedDate"] > datetime.fromtimestamp(1, pytz.utc)


@mock_secretsmanager
def test_delete_secret_force():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")

    result = conn.delete_secret(SecretId="test-secret", ForceDeleteWithoutRecovery=True)

    assert result["ARN"]
    assert result["DeletionDate"] > datetime.fromtimestamp(1, pytz.utc)
    assert result["Name"] == "test-secret"

    with pytest.raises(ClientError):
        result = conn.get_secret_value(SecretId="test-secret")


@mock_secretsmanager
def test_delete_secret_force_with_arn():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    create_secret = conn.create_secret(Name="test-secret", SecretString="foosecret")

    result = conn.delete_secret(
        SecretId=create_secret["ARN"], ForceDeleteWithoutRecovery=True
    )

    assert result["ARN"]
    assert result["DeletionDate"] > datetime.fromtimestamp(1, pytz.utc)
    assert result["Name"] == "test-secret"

    with pytest.raises(ClientError):
        result = conn.get_secret_value(SecretId="test-secret")


@mock_secretsmanager
def test_delete_secret_that_does_not_exist():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    with pytest.raises(ClientError):
        result = conn.delete_secret(
            SecretId="i-dont-exist", ForceDeleteWithoutRecovery=True
        )


@mock_secretsmanager
def test_delete_secret_fails_with_both_force_delete_flag_and_recovery_window_flag():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")

    with pytest.raises(ClientError):
        result = conn.delete_secret(
            SecretId="test-secret",
            RecoveryWindowInDays=1,
            ForceDeleteWithoutRecovery=True,
        )


@mock_secretsmanager
def test_delete_secret_recovery_window_too_short():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")

    with pytest.raises(ClientError):
        result = conn.delete_secret(SecretId="test-secret", RecoveryWindowInDays=6)


@mock_secretsmanager
def test_delete_secret_recovery_window_too_long():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")

    with pytest.raises(ClientError):
        result = conn.delete_secret(SecretId="test-secret", RecoveryWindowInDays=31)


@mock_secretsmanager
def test_delete_secret_that_is_marked_deleted():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")

    deleted_secret = conn.delete_secret(SecretId="test-secret")

    with pytest.raises(ClientError):
        result = conn.delete_secret(SecretId="test-secret")


@mock_secretsmanager
def test_get_random_password_default_length():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password()
    assert len(random_password["RandomPassword"]) == 32


@mock_secretsmanager
def test_get_random_password_default_requirements():
    # When require_each_included_type, default true
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password()
    # Should contain lowercase, upppercase, digit, special character
    assert any(c.islower() for c in random_password["RandomPassword"])
    assert any(c.isupper() for c in random_password["RandomPassword"])
    assert any(c.isdigit() for c in random_password["RandomPassword"])
    assert any(c in string.punctuation for c in random_password["RandomPassword"])


@mock_secretsmanager
def test_get_random_password_custom_length():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password(PasswordLength=50)
    assert len(random_password["RandomPassword"]) == 50


@mock_secretsmanager
def test_get_random_exclude_lowercase():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password(PasswordLength=55, ExcludeLowercase=True)
    assert not any(c.islower() for c in random_password["RandomPassword"])


@mock_secretsmanager
def test_get_random_exclude_uppercase():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password(PasswordLength=55, ExcludeUppercase=True)
    assert not any(c.isupper() for c in random_password["RandomPassword"])


@mock_secretsmanager
def test_get_random_exclude_characters_and_symbols():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password(
        PasswordLength=20, ExcludeCharacters="xyzDje@?!."
    )
    assert not any(c in "xyzDje@?!." for c in random_password["RandomPassword"])
    assert len(random_password["RandomPassword"]) == 20


@mock_secretsmanager
def test_get_random_exclude_numbers():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password(PasswordLength=100, ExcludeNumbers=True)
    assert not any(c.isdigit() for c in random_password["RandomPassword"])


@mock_secretsmanager
def test_get_random_exclude_punctuation():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password(
        PasswordLength=100, ExcludePunctuation=True
    )
    assert not any(c in string.punctuation for c in random_password["RandomPassword"])


@mock_secretsmanager
def test_get_random_include_space_false():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password(PasswordLength=300)
    assert not any(c.isspace() for c in random_password["RandomPassword"])


@mock_secretsmanager
def test_get_random_include_space_true():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password(PasswordLength=4, IncludeSpace=True)
    assert any(c.isspace() for c in random_password["RandomPassword"])


@mock_secretsmanager
def test_get_random_require_each_included_type():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    random_password = conn.get_random_password(
        PasswordLength=4, RequireEachIncludedType=True
    )
    assert any(c in string.punctuation for c in random_password["RandomPassword"])
    assert any(c in string.ascii_lowercase for c in random_password["RandomPassword"])
    assert any(c in string.ascii_uppercase for c in random_password["RandomPassword"])
    assert any(c in string.digits for c in random_password["RandomPassword"])


@mock_secretsmanager
def test_get_random_too_short_password():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    with pytest.raises(ClientError):
        random_password = conn.get_random_password(PasswordLength=3)


@mock_secretsmanager
def test_get_random_too_long_password():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    with pytest.raises(Exception):
        random_password = conn.get_random_password(PasswordLength=5555)


@mock_secretsmanager
def test_describe_secret():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name="test-secret", SecretString="foosecret")

    conn.create_secret(Name="test-secret-2", SecretString="barsecret")

    secret_description = conn.describe_secret(SecretId="test-secret")
    secret_description_2 = conn.describe_secret(SecretId="test-secret-2")

    assert secret_description  # Returned dict is not empty
    assert secret_description["Name"] == ("test-secret")
    assert secret_description["ARN"] != ""  # Test arn not empty
    assert secret_description_2["Name"] == ("test-secret-2")
    assert secret_description_2["ARN"] != ""  # Test arn not empty


@mock_secretsmanager
def test_describe_secret_with_arn():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    results = conn.create_secret(Name="test-secret", SecretString="foosecret")

    secret_description = conn.describe_secret(SecretId=results["ARN"])

    assert secret_description  # Returned dict is not empty
    secret_description["Name"].should.equal("test-secret")
    secret_description["ARN"].should.equal(results["ARN"])
    conn.list_secrets()["SecretList"][0]["ARN"].should.equal(results["ARN"])


@mock_secretsmanager
def test_describe_secret_that_does_not_exist():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    with pytest.raises(ClientError):
        result = conn.get_secret_value(SecretId="i-dont-exist")


@mock_secretsmanager
def test_describe_secret_that_does_not_match():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name="test-secret", SecretString="foosecret")

    with pytest.raises(ClientError):
        result = conn.get_secret_value(SecretId="i-dont-match")


@mock_secretsmanager
def test_restore_secret():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")

    conn.delete_secret(SecretId="test-secret")

    described_secret_before = conn.describe_secret(SecretId="test-secret")
    assert described_secret_before["DeletedDate"] > datetime.fromtimestamp(1, pytz.utc)

    restored_secret = conn.restore_secret(SecretId="test-secret")
    assert restored_secret["ARN"]
    assert restored_secret["Name"] == "test-secret"

    described_secret_after = conn.describe_secret(SecretId="test-secret")
    assert "DeletedDate" not in described_secret_after


@mock_secretsmanager
def test_restore_secret_that_is_not_deleted():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")

    restored_secret = conn.restore_secret(SecretId="test-secret")
    assert restored_secret["ARN"]
    assert restored_secret["Name"] == "test-secret"


@mock_secretsmanager
def test_restore_secret_that_does_not_exist():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    with pytest.raises(ClientError):
        result = conn.restore_secret(SecretId="i-dont-exist")


@mock_secretsmanager
def test_rotate_secret():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(
        Name=DEFAULT_SECRET_NAME, SecretString="foosecret", Description="foodescription"
    )

    rotated_secret = conn.rotate_secret(SecretId=DEFAULT_SECRET_NAME)

    assert rotated_secret
    assert rotated_secret["ARN"] != ""  # Test arn not empty
    assert rotated_secret["Name"] == DEFAULT_SECRET_NAME
    assert rotated_secret["VersionId"] != ""

    describe_secret = conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)

    assert describe_secret["Description"] == "foodescription"


@mock_secretsmanager
def test_rotate_secret_enable_rotation():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretString="foosecret")

    initial_description = conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)
    assert initial_description
    assert initial_description["RotationEnabled"] is False
    assert initial_description["RotationRules"]["AutomaticallyAfterDays"] == 0

    conn.rotate_secret(
        SecretId=DEFAULT_SECRET_NAME, RotationRules={"AutomaticallyAfterDays": 42}
    )

    rotated_description = conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)
    assert rotated_description
    assert rotated_description["RotationEnabled"] is True
    assert rotated_description["RotationRules"]["AutomaticallyAfterDays"] == 42


@mock_secretsmanager
def test_rotate_secret_that_is_marked_deleted():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    conn.create_secret(Name="test-secret", SecretString="foosecret")

    conn.delete_secret(SecretId="test-secret")

    with pytest.raises(ClientError):
        result = conn.rotate_secret(SecretId="test-secret")


@mock_secretsmanager
def test_rotate_secret_that_does_not_exist():
    conn = boto3.client("secretsmanager", "us-west-2")

    with pytest.raises(ClientError):
        result = conn.rotate_secret(SecretId="i-dont-exist")


@mock_secretsmanager
def test_rotate_secret_that_does_not_match():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name="test-secret", SecretString="foosecret")

    with pytest.raises(ClientError):
        result = conn.rotate_secret(SecretId="i-dont-match")


@mock_secretsmanager
def test_rotate_secret_client_request_token_too_short():
    # Test is intentionally empty. Boto3 catches too short ClientRequestToken
    # and raises ParamValidationError before Moto can see it.
    # test_server actually handles this error.
    assert True


@mock_secretsmanager
def test_rotate_secret_client_request_token_too_long():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretString="foosecret")

    client_request_token = (
        "ED9F8B6C-85B7-446A-B7E4-38F2A3BEB13C-" "ED9F8B6C-85B7-446A-B7E4-38F2A3BEB13C"
    )
    with pytest.raises(ClientError):
        result = conn.rotate_secret(
            SecretId=DEFAULT_SECRET_NAME, ClientRequestToken=client_request_token
        )


@mock_secretsmanager
def test_rotate_secret_rotation_lambda_arn_too_long():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretString="foosecret")

    rotation_lambda_arn = "85B7-446A-B7E4" * 147  # == 2058 characters
    with pytest.raises(ClientError):
        result = conn.rotate_secret(
            SecretId=DEFAULT_SECRET_NAME, RotationLambdaARN=rotation_lambda_arn
        )


@mock_secretsmanager
def test_rotate_secret_rotation_period_zero():
    # Test is intentionally empty. Boto3 catches zero day rotation period
    # and raises ParamValidationError before Moto can see it.
    # test_server actually handles this error.
    assert True


@mock_secretsmanager
def test_rotate_secret_rotation_period_too_long():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretString="foosecret")

    rotation_rules = {"AutomaticallyAfterDays": 1001}
    with pytest.raises(ClientError):
        result = conn.rotate_secret(
            SecretId=DEFAULT_SECRET_NAME, RotationRules=rotation_rules
        )


def get_rotation_zip_file():
    from tests.test_awslambda.test_lambda import _process_lambda

    func_str = """
import boto3
import json

def lambda_handler(event, context):
    arn = event['SecretId']
    token = event['ClientRequestToken']
    step = event['Step']
    
    client = boto3.client("secretsmanager", region_name="us-west-2", endpoint_url="http://motoserver:5000")
    metadata = client.describe_secret(SecretId=arn)
    value = client.get_secret_value(SecretId=arn, VersionId=token, VersionStage="AWSPENDING")

    if not metadata['RotationEnabled']:
        print("Secret %s is not enabled for rotation." % arn)
        raise ValueError("Secret %s is not enabled for rotation." % arn)
    versions = metadata['VersionIdsToStages']
    if token not in versions:
        print("Secret version %s has no stage for rotation of secret %s." % (token, arn))
        raise ValueError("Secret version %s has no stage for rotation of secret %s." % (token, arn))
    if "AWSCURRENT" in versions[token]:
        print("Secret version %s already set as AWSCURRENT for secret %s." % (token, arn))
        return
    elif "AWSPENDING" not in versions[token]:
        print("Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn))
        raise ValueError("Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn))
    
    if step == 'createSecret':
        try:
            client.get_secret_value(SecretId=arn, VersionId=token, VersionStage='AWSPENDING')
        except client.exceptions.ResourceNotFoundException:
            client.put_secret_value(
                SecretId=arn, 
                ClientRequestToken=token, 
                SecretString=json.dumps({'create': True}), 
                VersionStages=['AWSPENDING']
            )

    if step == 'setSecret':
        client.put_secret_value(
            SecretId=arn, 
            ClientRequestToken=token, 
            SecretString='UpdatedValue', 
            VersionStages=["AWSPENDING"],
        )
    
    elif step == 'finishSecret':
        current_version = next(
            version
            for version, stages in metadata['VersionIdsToStages'].items()
            if 'AWSCURRENT' in stages 
        )
        print("current: %s new: %s" % (current_version, token))
        client.update_secret_version_stage(
            SecretId=arn,
            VersionStage='AWSCURRENT',
            MoveToVersionId=token,
            RemoveFromVersionId=current_version,
        )
        client.update_secret_version_stage(
            SecretId=arn,
            VersionStage='AWSPENDING',
            RemoveFromVersionId=token,
        )
    """
    return _process_lambda(func_str)


if settings.TEST_SERVER_MODE:

    @mock_lambda
    @mock_secretsmanager
    def test_rotate_secret_using_lambda():
        from tests.test_awslambda.test_lambda import get_role_name

        # Passing a `RotationLambdaARN` value to `rotate_secret` should invoke lambda
        lambda_conn = boto3.client(
            "lambda", region_name="us-west-2", endpoint_url="http://localhost:5000",
        )
        func = lambda_conn.create_function(
            FunctionName="testFunction",
            Runtime="python3.8",
            Role=get_role_name(),
            Handler="lambda_function.lambda_handler",
            Code={"ZipFile": get_rotation_zip_file()},
            Description="Secret rotator",
            Timeout=3,
            MemorySize=128,
            Publish=True,
        )

        secrets_conn = boto3.client(
            "secretsmanager",
            region_name="us-west-2",
            endpoint_url="http://localhost:5000",
        )
        secret = secrets_conn.create_secret(
            Name=DEFAULT_SECRET_NAME, SecretString="InitialValue",
        )
        initial_version = secret["VersionId"]

        rotated_secret = secrets_conn.rotate_secret(
            SecretId=DEFAULT_SECRET_NAME,
            RotationLambdaARN=func["FunctionArn"],
            RotationRules=dict(AutomaticallyAfterDays=30,),
        )

        # Ensure we received an updated VersionId from `rotate_secret`
        assert rotated_secret["VersionId"] != initial_version

        updated_secret = secrets_conn.get_secret_value(
            SecretId=DEFAULT_SECRET_NAME, VersionStage="AWSCURRENT",
        )
        rotated_version = updated_secret["VersionId"]

        assert initial_version != rotated_version
        metadata = secrets_conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)
        assert metadata["VersionIdsToStages"][initial_version] == ["AWSPREVIOUS"]
        assert metadata["VersionIdsToStages"][rotated_version] == ["AWSCURRENT"]
        assert updated_secret["SecretString"] == "UpdatedValue"


@mock_secretsmanager
def test_put_secret_value_on_non_existing_secret():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    with pytest.raises(ClientError) as cm:
        conn.put_secret_value(
            SecretId=DEFAULT_SECRET_NAME,
            SecretString="foosecret",
            VersionStages=["AWSCURRENT"],
        )

    cm.value.response["Error"]["Message"].should.equal(
        "Secrets Manager can't find the specified secret."
    )


@mock_secretsmanager
def test_put_secret_value_puts_new_secret():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b("foosecret"))
    put_secret_value_dict = conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="foosecret",
        VersionStages=["AWSCURRENT"],
    )
    version_id = put_secret_value_dict["VersionId"]

    get_secret_value_dict = conn.get_secret_value(
        SecretId=DEFAULT_SECRET_NAME, VersionId=version_id, VersionStage="AWSCURRENT"
    )

    assert get_secret_value_dict
    assert get_secret_value_dict["SecretString"] == "foosecret"


@mock_secretsmanager
def test_put_secret_binary_value_puts_new_secret():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b("foosecret"))
    put_secret_value_dict = conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretBinary=b("foosecret"),
        VersionStages=["AWSCURRENT"],
    )
    version_id = put_secret_value_dict["VersionId"]

    get_secret_value_dict = conn.get_secret_value(
        SecretId=DEFAULT_SECRET_NAME, VersionId=version_id, VersionStage="AWSCURRENT"
    )

    assert get_secret_value_dict
    assert get_secret_value_dict["SecretBinary"] == b("foosecret")


@mock_secretsmanager
def test_create_and_put_secret_binary_value_puts_new_secret():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b("foosecret"))
    conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME, SecretBinary=b("foosecret_update")
    )

    latest_secret = conn.get_secret_value(SecretId=DEFAULT_SECRET_NAME)

    assert latest_secret
    assert latest_secret["SecretBinary"] == b("foosecret_update")


@mock_secretsmanager
def test_put_secret_binary_requires_either_string_or_binary():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    with pytest.raises(ClientError) as ire:
        conn.put_secret_value(SecretId=DEFAULT_SECRET_NAME)

    ire.value.response["Error"]["Code"].should.equal("InvalidRequestException")
    ire.value.response["Error"]["Message"].should.equal(
        "You must provide either SecretString or SecretBinary."
    )


@mock_secretsmanager
def test_put_secret_value_can_get_first_version_if_put_twice():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b("foosecret"))
    put_secret_value_dict = conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="first_secret",
        VersionStages=["AWSCURRENT"],
    )
    first_version_id = put_secret_value_dict["VersionId"]
    conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="second_secret",
        VersionStages=["AWSCURRENT"],
    )

    first_secret_value_dict = conn.get_secret_value(
        SecretId=DEFAULT_SECRET_NAME, VersionId=first_version_id
    )
    first_secret_value = first_secret_value_dict["SecretString"]

    assert first_secret_value == "first_secret"


@mock_secretsmanager
def test_put_secret_value_versions_differ_if_same_secret_put_twice():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary="foosecret")
    put_secret_value_dict = conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="dupe_secret",
        VersionStages=["AWSCURRENT"],
    )
    first_version_id = put_secret_value_dict["VersionId"]
    put_secret_value_dict = conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="dupe_secret",
        VersionStages=["AWSCURRENT"],
    )
    second_version_id = put_secret_value_dict["VersionId"]

    assert first_version_id != second_version_id


@mock_secretsmanager
def test_put_secret_value_maintains_description_and_tags():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    previous_response = conn.create_secret(
        Name=DEFAULT_SECRET_NAME,
        SecretString="foosecret",
        Description="desc",
        Tags=[{"Key": "Foo", "Value": "Bar"}, {"Key": "Mykey", "Value": "Myvalue"}],
    )
    previous_version_id = previous_response["VersionId"]

    conn = boto3.client("secretsmanager", region_name="us-west-2")
    current_response = conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="dupe_secret",
        VersionStages=["AWSCURRENT"],
    )
    current_version_id = current_response["VersionId"]

    secret_details = conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)
    assert secret_details["Tags"] == [
        {"Key": "Foo", "Value": "Bar"},
        {"Key": "Mykey", "Value": "Myvalue"},
    ]
    assert secret_details["Description"] == "desc"
    assert secret_details["VersionIdsToStages"] is not None
    assert previous_version_id in secret_details["VersionIdsToStages"]
    assert current_version_id in secret_details["VersionIdsToStages"]
    assert secret_details["VersionIdsToStages"][previous_version_id] == ["AWSPREVIOUS"]
    assert secret_details["VersionIdsToStages"][current_version_id] == ["AWSCURRENT"]


@mock_secretsmanager
def test_can_list_secret_version_ids():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary="foosecret")
    put_secret_value_dict = conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="dupe_secret",
        VersionStages=["AWSCURRENT"],
    )
    first_version_id = put_secret_value_dict["VersionId"]
    put_secret_value_dict = conn.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="dupe_secret",
        VersionStages=["AWSCURRENT"],
    )
    second_version_id = put_secret_value_dict["VersionId"]

    versions_list = conn.list_secret_version_ids(SecretId=DEFAULT_SECRET_NAME)

    returned_version_ids = [v["VersionId"] for v in versions_list["Versions"]]

    assert [first_version_id, second_version_id].sort() == returned_version_ids.sort()


@mock_secretsmanager
def test_update_secret():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    created_secret = conn.create_secret(Name="test-secret", SecretString="foosecret")

    assert created_secret["ARN"]
    assert created_secret["Name"] == "test-secret"
    assert created_secret["VersionId"] != ""

    secret = conn.get_secret_value(SecretId="test-secret")
    assert secret["SecretString"] == "foosecret"

    updated_secret = conn.update_secret(
        SecretId="test-secret", SecretString="barsecret"
    )

    assert updated_secret["ARN"]
    assert updated_secret["Name"] == "test-secret"
    assert updated_secret["VersionId"] != ""

    secret = conn.get_secret_value(SecretId="test-secret")
    assert secret["SecretString"] == "barsecret"
    assert created_secret["VersionId"] != updated_secret["VersionId"]


@mock_secretsmanager
def test_update_secret_with_tags_and_description():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    created_secret = conn.create_secret(
        Name="test-secret",
        SecretString="foosecret",
        Description="desc",
        Tags=[{"Key": "Foo", "Value": "Bar"}, {"Key": "Mykey", "Value": "Myvalue"}],
    )

    assert created_secret["ARN"]
    assert created_secret["Name"] == "test-secret"
    assert created_secret["VersionId"] != ""

    secret = conn.get_secret_value(SecretId="test-secret")
    assert secret["SecretString"] == "foosecret"

    updated_secret = conn.update_secret(
        SecretId="test-secret", SecretString="barsecret"
    )

    assert updated_secret["ARN"]
    assert updated_secret["Name"] == "test-secret"
    assert updated_secret["VersionId"] != ""

    secret = conn.get_secret_value(SecretId="test-secret")
    assert secret["SecretString"] == "barsecret"
    assert created_secret["VersionId"] != updated_secret["VersionId"]
    secret_details = conn.describe_secret(SecretId="test-secret")
    assert secret_details["Tags"] == [
        {"Key": "Foo", "Value": "Bar"},
        {"Key": "Mykey", "Value": "Myvalue"},
    ]
    assert secret_details["Description"] == "desc"


@mock_secretsmanager
def test_update_secret_which_does_not_exit():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    with pytest.raises(ClientError) as cm:
        updated_secret = conn.update_secret(
            SecretId="test-secret", SecretString="barsecret"
        )

    assert (
        "Secrets Manager can't find the specified secret."
        == cm.value.response["Error"]["Message"]
    )


@mock_secretsmanager
def test_update_secret_marked_as_deleted():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    created_secret = conn.create_secret(Name="test-secret", SecretString="foosecret")
    deleted_secret = conn.delete_secret(SecretId="test-secret")

    with pytest.raises(ClientError) as cm:
        updated_secret = conn.update_secret(
            SecretId="test-secret", SecretString="barsecret"
        )

    assert (
        "because it was marked for deletion." in cm.value.response["Error"]["Message"]
    )


@mock_secretsmanager
def test_update_secret_marked_as_deleted_after_restoring():
    conn = boto3.client("secretsmanager", region_name="us-west-2")

    created_secret = conn.create_secret(Name="test-secret", SecretString="foosecret")
    deleted_secret = conn.delete_secret(SecretId="test-secret")
    restored_secret = conn.restore_secret(SecretId="test-secret")

    updated_secret = conn.update_secret(
        SecretId="test-secret", SecretString="barsecret"
    )

    assert updated_secret["ARN"]
    assert updated_secret["Name"] == "test-secret"
    assert updated_secret["VersionId"] != ""


@mock_secretsmanager
def test_tag_resource():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name="test-secret", SecretString="foosecret")
    conn.tag_resource(
        SecretId="test-secret", Tags=[{"Key": "FirstTag", "Value": "SomeValue"},],
    )

    conn.tag_resource(
        SecretId="test-secret", Tags=[{"Key": "SecondTag", "Value": "AnotherValue"},],
    )

    secrets = conn.list_secrets()
    assert secrets["SecretList"][0].get("Tags") == [
        {"Key": "FirstTag", "Value": "SomeValue"},
        {"Key": "SecondTag", "Value": "AnotherValue"},
    ]

    with pytest.raises(ClientError) as cm:
        conn.tag_resource(
            SecretId="dummy-test-secret",
            Tags=[{"Key": "FirstTag", "Value": "SomeValue"},],
        )

    assert (
        "Secrets Manager can't find the specified secret."
        == cm.value.response["Error"]["Message"]
    )


@mock_secretsmanager
def test_untag_resource():
    conn = boto3.client("secretsmanager", region_name="us-west-2")
    conn.create_secret(Name="test-secret", SecretString="foosecret")
    conn.tag_resource(
        SecretId="test-secret",
        Tags=[
            {"Key": "FirstTag", "Value": "SomeValue"},
            {"Key": "SecondTag", "Value": "SomeValue"},
        ],
    )

    conn.untag_resource(SecretId="test-secret", TagKeys=["FirstTag"])
    secrets = conn.list_secrets()
    assert secrets["SecretList"][0].get("Tags") == [
        {"Key": "SecondTag", "Value": "SomeValue"},
    ]

    with pytest.raises(ClientError) as cm:
        conn.untag_resource(
            SecretId="dummy-test-secret", TagKeys=["FirstTag"],
        )

    assert (
        "Secrets Manager can't find the specified secret."
        == cm.value.response["Error"]["Message"]
    )


@mock_secretsmanager
def test_secret_versions_to_stages_attribute_discrepancy():
    client = boto3.client("secretsmanager", region_name="us-west-2")

    resp = client.create_secret(Name=DEFAULT_SECRET_NAME, SecretString="foosecret")
    previous_version_id = resp["VersionId"]

    resp = client.put_secret_value(
        SecretId=DEFAULT_SECRET_NAME,
        SecretString="dupe_secret",
        VersionStages=["AWSCURRENT"],
    )
    current_version_id = resp["VersionId"]

    secret = client.describe_secret(SecretId=DEFAULT_SECRET_NAME)
    describe_vtos = secret["VersionIdsToStages"]
    assert describe_vtos[current_version_id] == ["AWSCURRENT"]
    assert describe_vtos[previous_version_id] == ["AWSPREVIOUS"]

    secret = client.list_secrets(
        Filters=[{"Key": "name", "Values": [DEFAULT_SECRET_NAME]}]
    ).get("SecretList")[0]
    list_vtos = secret["SecretVersionsToStages"]
    assert list_vtos[current_version_id] == ["AWSCURRENT"]
    assert list_vtos[previous_version_id] == ["AWSPREVIOUS"]

    assert describe_vtos == list_vtos
