import datetime
import errno
import logging
import os
from uuid import UUID

import click
import pytest
import yaml
from botocore.exceptions import ClientError
from click.testing import CliRunner
from mock import MagicMock, patch, sentinel

from awsscripter.cli import cli
from awsscripter.common.config_reader import ConfigReader
from awsscripter.common.environment import Environment
from awsscripter.common.exceptions import awsscripterException
from awsscripter.stack.stack import Stack
from awsscripter.stack.helpers import CustomJsonEncoder, catch_exceptions
from awsscripter.stack.helpers import get_stack_or_env
from awsscripter.stack.helpers import setup_logging, write, ColouredFormatter
from awsscripter.stack.stack_status import StackStatus, StackChangeSetStatus


class TestCli(object):

    def setup_method(self, test_method):
        self.patcher_ConfigReader = patch("awsscripter.common.config_reader.ConfigReader")
        self.patcher_getcwd = patch("awsscripter.cli.os.getcwd")

        self.mock_ConfigReader = self.patcher_ConfigReader.start()
        self.mock_getcwd = self.patcher_getcwd.start()

        self.mock_config_reader = MagicMock(spec=ConfigReader)
        self.mock_stack = MagicMock(spec=Stack)
        self.mock_environment = MagicMock(spec=Environment)

        self.mock_config_reader.construct_stack.return_value = self.mock_stack
        self.mock_config_reader.construct_environment.return_value = \
            self.mock_environment

        self.mock_ConfigReader.return_value = self.mock_config_reader
        self.mock_getcwd.return_value = sentinel.cwd

        self.runner = CliRunner()

    def teardown_method(self, test_method):
        self.patcher_ConfigReader.stop()
        self.patcher_getcwd.stop()

    @patch("sys.exit")
    def test_catch_excecptions(self, mock_exit):
        @catch_exceptions
        def raises_exception():
            raise awsscripterException()

        raises_exception()
        mock_exit.assert_called_once_with(1)

    @pytest.mark.parametrize("command,files,output", [
        # one --var option
        (
            ["--var", "a=1", "noop"],
            {},
            {"a": "1"}
        ),
        # multiple --var options
        (
            ["--var", "a=1", "--var", "b=2", "noop"],
            {},
            {"a": "1", "b": "2"}
        ),
        # one --var-file option
        (
            ["--var-file", "foo.yaml", "noop"],
            {
                "foo.yaml": {"key1": "val1", "key2": "val2"}
            },
            {"key1": "val1", "key2": "val2"}
        ),
        # multiple --var-file option
        (
            ["--var-file", "foo.yaml", "--var-file", "bar.yaml", "noop"],
            {
                "foo.yaml": {"key1": "parent_value1", "key2": "parent_value2"},
                "bar.yaml": {"key2": "child_value2", "key3": "child_value3"}
            },
            {
                "key1": "parent_value1",
                "key2": "child_value2",
                "key3": "child_value3"
            }
        ),
        # mix of --var and --var-file
        (
            ["--var-file", "foo.yaml", "--var", "key2=var2", "noop"],
            {
                "foo.yaml": {"key1": "file1", "key2": "file2"}
            },
            {"key1": "file1", "key2": "var2"}
        ),
    ])
    def test_user_variables(self, command, files, output):
        @cli.command()
        @click.pass_context
        def noop(ctx):
            click.echo(yaml.safe_dump(ctx.obj["user_variables"]))

        self.patcher_getcwd.stop()
        with self.runner.isolated_filesystem():
            for name, content in files.items():
                with open(name, "w") as fh:
                    yaml.safe_dump(content, fh)

            result = self.runner.invoke(cli, command)
        self.patcher_getcwd.start()

        user_variables = yaml.safe_load(result.output)
        assert result.exit_code == 0
        assert user_variables == output

    def test_validate_template_with_valid_template(self):
        self.mock_stack.template.validate.return_value = {
                    "Parameters": "Example",
                    "ResponseMetadata": {
                        "HTTPStatusCode": 200
                    }
                }
        result = self.runner.invoke(cli, ["validate", "dev/vpc.yaml"])
        self.mock_stack.template.validate.assert_called_with()

        assert result.output == "Template is valid. Template details:\n\n" \
            "Parameters: Example\n\n"

    def test_validate_template_with_invalid_template(self):
        client_error = ClientError(
            {
                "Errors":
                {
                    "Message": "Unrecognized resource types",
                    "Code": "ValidationError",
                }
            },
            "ValidateTemplate"
        )
        self.mock_stack.template.validate.side_effect = client_error

        expected_result = str(client_error) + "\n"
        result = self.runner.invoke(cli, ["validate", "dev/vpc.yaml"])
        assert result.output == expected_result

    def test_generate_template(self):
        self.mock_stack.template.body = "body"
        result = self.runner.invoke(cli, ["generate", "dev/vpc.yaml"])
        self.mock_config_reader.construct_stack.assert_called_with(
            "dev/vpc.yaml"
        )

        assert result.output == "body\n"

    def test_lock_stack(self):
        self.runner.invoke(
            cli, ["set-policy", "dev/vpc.yaml", "-b", "deny-all"]
        )
        self.mock_config_reader.construct_stack.assert_called_with(
                "dev/vpc.yaml"
        )
        self.mock_stack.lock.assert_called_with()

    def test_unlock_stack(self):
        self.runner.invoke(
            cli, ["set-policy", "dev/vpc.yaml", "-b", "allow-all"]
        )
        self.mock_config_reader.construct_stack.assert_called_with(
                "dev/vpc.yaml"
        )
        self.mock_stack.unlock.assert_called_with()

    def test_set_policy_with_file_flag(self):
        policy_file = "tests/fixtures/stack_policies/lock.json"
        result = self.runner.invoke(cli, [
            "set-policy", "dev/vpc.yaml", policy_file
        ])
        assert result.exit_code == 0
        self.mock_stack.set_policy.assert_called_once_with(policy_file)

    def test_describe_policy_with_existing_policy(self):
        self.mock_stack.get_policy.return_value = {
            "StackPolicyBody": "Body"
        }

        result = self.runner.invoke(
            cli, ["describe", "policy", "dev/vpc.yaml"]
        )
        assert result.exit_code == 0
        assert result.output == "Body\n"

    def test_list_env_resources(self):
        response = {
            "stack-name-1": {
                "StackResources": [
                    {
                        "LogicalResourceId": "logical-resource-id",
                        "PhysicalResourceId": "physical-resource-id"
                    }
                ]
            },
            "stack-name-2": {
                "StackResources": [
                    {
                        "LogicalResourceId": "logical-resource-id",
                        "PhysicalResourceId": "physical-resource-id"
                    }
                ]
            }
        }
        self.mock_environment.describe_resources.return_value = response
        result = self.runner.invoke(cli, ["list", "resources", "dev"])
        assert yaml.load(result.output) == response
        assert result.exit_code == 0

    def test_list_stack_resources(self):
        response = {
                "StackResources": [
                    {
                        "LogicalResourceId": "logical-resource-id",
                        "PhysicalResourceId": "physical-resource-id"
                    }
                ]
            }
        self.mock_stack.describe_resources.return_value = response
        result = self.runner.invoke(cli, ["stack", "list", "resources", "dev/vpc.yaml"])
        assert yaml.load(result.output) == response
        assert result.exit_code == 0

    def test_create_envorstack(self):
        result = self.runner.invoke(cli, ["--debug","--dir","D:\\mycode\\examples\\baseecscluster","stack", "launch", "dev"])
        assert result.exit_code == 0

    @pytest.mark.parametrize(
        "command,success,yes_flag,exit_code", [
            ("create", True, True, 0),
            ("create", False, True, 1),
            ("create", True, False, 0),
            ("create", False, False, 1),
            ("delete", True, True, 0),
            ("delete", False, True, 1),
            ("delete", True, False, 0),
            ("delete", False, False, 1),
            ("update", True, True, 0),
            ("update", False, True, 1),
            ("update", True, False, 0),
            ("update", False, False, 1),
            ("launch", True, True, 0),
            ("launch", False, True, 1),
            ("launch", True, False, 0),
            ("launch", False, False, 1)
        ]
    )
    def test_stack_commands(self, command, success, yes_flag, exit_code):
        getattr(self.mock_stack, command).return_value = \
            StackStatus.COMPLETE if success else StackStatus.FAILED

        kwargs = {"args": [command, "dev/vpc.yaml"]}
        if yes_flag:
            kwargs["args"].append("-y")
        else:
            kwargs["input"] = "y\n"

        result = self.runner.invoke(cli, **kwargs)

        getattr(self.mock_stack, command).assert_called_with()
        assert result.exit_code == exit_code

    @pytest.mark.parametrize(
        "command,yes_flag", [
            ("create", True),
            ("create", False),
            ("delete", True),
            ("delete", False),
            ("execute", True),
            ("execute", False)
        ]
    )
    def test_change_set_commands(self, command, yes_flag):
        stack_command = command + "_change_set"

        kwargs = {"args": [command, "dev/vpc.yaml", "cs1"]}
        if yes_flag:
            kwargs["args"].append("-y")
        else:
            kwargs["input"] = "y\n"

        result = self.runner.invoke(cli, **kwargs)

        getattr(self.mock_stack, stack_command).assert_called_with("cs1")
        assert result.exit_code == 0

    @pytest.mark.parametrize(
        "command,success,yes_flag,exit_code", [
            ("delete", True, True, 0),
            ("delete", False, True, 1),
            ("delete", True, False, 0),
            ("delete", False, False, 1),
            ("launch", True, True, 0),
            ("launch", False, True, 1),
            ("launch", True, False, 0),
            ("launch", False, False, 1)
        ]
    )
    def test_env_commands(self, command, success, yes_flag, exit_code):
        status = StackStatus.COMPLETE if success else StackStatus.FAILED
        response = {"stack": status}

        getattr(self.mock_environment, command).return_value = response

        kwargs = {"args": [command, "dev"]}
        if yes_flag:
            kwargs["args"].append("-y")
        else:
            kwargs["input"] = "y\n"

        result = self.runner.invoke(cli, **kwargs)

        getattr(self.mock_environment, command).assert_called_with()
        assert result.exit_code == exit_code

    @patch('awsscripter.cli.update.uuid1')
    @patch('awsscripter.cli.update.write')
    @pytest.mark.parametrize(
        "verbose_flag,yes_flag,cs_success", [
            (False, True, True),
            (False, False, True),
            (True, True, True),
            (True, False, True),
            (False, True, False),
            (False, False, False),
            (True, True, False),
            (True, False, False)
        ]
    )
    def test_update_with_change_set_with_input_yes(
        self, mock_write, mock_uuid1, verbose_flag, yes_flag, cs_success
    ):
        self.mock_stack.wait_for_cs_completion.return_value = \
            StackChangeSetStatus.READY if cs_success \
            else StackChangeSetStatus.DEFUNCT
        response = {
            "VerboseProperty": "VerboseProperty",
            "ChangeSetName": "ChangeSetName",
            "CreationTime": "CreationTime",
            "ExecutionStatus": "ExecutionStatus",
            "StackName": "StackName",
            "Status": "Status",
            "StatusReason": "StatusReason",
            "Changes": [
                {
                    "ResourceChange": {
                        "Action": "Action",
                        "LogicalResourceId": "LogicalResourceId",
                        "PhysicalResourceId": "PhysicalResourceId",
                        "Replacement": "Replacement",
                        "ResourceType": "ResourceType",
                        "Scope": "Scope",
                        "VerboseProperty": "VerboseProperty"
                    }
                }
            ]
        }
        self.mock_stack.describe_change_set.return_value = response

        mock_uuid1.return_value = UUID(int=0)

        kwargs = {"args": ["update", "dev/vpc.yaml", "-c"]}

        if verbose_flag:
            kwargs["args"].append("-v")

        if yes_flag:
            kwargs["args"].append("-y")
        else:
            kwargs["input"] = "y\n"

        result = self.runner.invoke(cli, **kwargs)

        if cs_success:
            if not verbose_flag:
                del response["VerboseProperty"]
                del response["Changes"][0]["ResourceChange"]["VerboseProperty"]
            mock_write.assert_called_once_with(response, 'yaml')
            self.mock_stack.execute_change_set.assert_called_once_with(
                "change-set-00000000000000000000000000000000"
            )

        self.mock_stack.delete_change_set.assert_called_once_with(
            "change-set-00000000000000000000000000000000"
        )

        assert result.exit_code == (0 if cs_success else 1)

    @pytest.mark.parametrize(
        "verbose_flag,", [
            (False),
            (True)
        ]
    )
    def test_describe_change_set(self, verbose_flag):
        response = {
            "VerboseProperty": "VerboseProperty",
            "ChangeSetName": "ChangeSetName",
            "CreationTime": "CreationTime",
            "ExecutionStatus": "ExecutionStatus",
            "StackName": "StackName",
            "Status": "Status",
            "StatusReason": "StatusReason",
            "Changes": [
                {
                    "ResourceChange": {
                        "Action": "Action",
                        "LogicalResourceId": "LogicalResourceId",
                        "PhysicalResourceId": "PhysicalResourceId",
                        "Replacement": "Replacement",
                        "ResourceType": "ResourceType",
                        "Scope": "Scope",
                        "VerboseProperty": "VerboseProperty"
                    }
                }
            ]
        }
        args = ["describe", "change-set", "dev/vpc.yaml", "cs1"]
        if verbose_flag:
            args.append("-v")

        self.mock_stack.describe_change_set.return_value = response
        result = self.runner.invoke(cli, args)
        if not verbose_flag:
            del response["VerboseProperty"]
            del response["Changes"][0]["ResourceChange"]["VerboseProperty"]
        assert yaml.load(result.output) == response
        assert result.exit_code == 0

    def test_list_change_sets_with_200(self):
        self.mock_stack.list_change_sets.return_value = {
            "ResponseMetadata": {
                "HTTPStatusCode": 200
            },
            "ChangeSets": "Test"
        }
        result = self.runner.invoke(
            cli, ["list", "change-sets", "dev/vpc.yaml"]
        )
        assert result.exit_code == 0
        assert yaml.load(result.output) == {"ChangeSets": "Test"}

    def test_list_change_sets_without_200(self):
        response = {
            "ResponseMetadata": {
                "HTTPStatusCode": 404
            },
            "ChangeSets": "Test"
        }
        self.mock_stack.list_change_sets.return_value = response

        result = self.runner.invoke(
            cli, ["list", "change-sets", "dev/vpc.yaml"]
        )
        assert result.exit_code == 0
        assert yaml.load(result.output) == response

    def test_list_outputs(self):
        outputs = [{"OutputKey": "Key", "OutputValue": "Value"}]
        self.mock_stack.describe_outputs.return_value = outputs
        result = self.runner.invoke(
            cli, ["list", "outputs", "dev/vpc.yaml"]
        )
        assert result.exit_code == 0
        assert yaml.load(result.output) == outputs

    def test_list_outputs_with_export(self):
        outputs = [{"OutputKey": "Key", "OutputValue": "Value"}]
        self.mock_stack.describe_outputs.return_value = outputs
        result = self.runner.invoke(
            cli, ["list", "outputs", "dev/vpc.yaml", "-e", "envvar"]
        )
        assert result.exit_code == 0
        assert yaml.load(result.output) == "export SCEPTRE_Key=Value"

    def test_status_with_env(self):
        self.mock_environment.describe.return_value = {"stack": "status"}

        result = self.runner.invoke(cli, ["status", "dev"])
        assert result.exit_code == 0
        assert result.output == "stack: status\n\n"

    def test_status_with_stack(self):
        self.mock_stack.get_status.return_value = "status"

        result = self.runner.invoke(cli, ["status", "dev/vpc.yaml"])
        assert result.exit_code == 0
        assert result.output == "status\n"

    def test_init_project_non_existant(self):
        self.patcher_getcwd.stop()
        with self.runner.isolated_filesystem():
            sceptre_dir = os.path.abspath('./example')
            config_dir = os.path.join(sceptre_dir, "config")
            template_dir = os.path.join(sceptre_dir, "templates")
            region = "test-region"
            os.environ["AWS_DEFAULT_REGION"] = region
            defaults = {
                "project_code": "example",
                "region": region
            }

            result = self.runner.invoke(cli, ["init1", "project", "example"])
            assert not result.exception
            assert os.path.isdir(config_dir)
            assert os.path.isdir(template_dir)

            with open(os.path.join(config_dir, "config.yaml")) as config_file:
                config = yaml.load(config_file)

            assert config == defaults
        self.patcher_getcwd.start()

    def test_init_project_already_exist(self):
        self.patcher_getcwd.stop()
        with self.runner.isolated_filesystem():
            sceptre_dir = os.path.abspath('./example')
            config_dir = os.path.join(sceptre_dir, "config")
            template_dir = os.path.join(sceptre_dir, "templates")
            existing_config = {"Test": "Test"}

            os.mkdir(sceptre_dir)
            os.mkdir(config_dir)
            os.mkdir(template_dir)

            config_filepath = os.path.join(config_dir, "config.yaml")
            with open(config_filepath, 'w') as config_file:
                yaml.dump(existing_config, config_file)

            result = self.runner.invoke(cli, ["init1", "project", "example"])
            assert result.exit_code == 1
            assert result.output == 'Folder \"example\" already exists.\n'
            assert os.path.isdir(config_dir)
            assert os.path.isdir(template_dir)

            with open(os.path.join(config_dir, "config.yaml")) as config_file:
                config = yaml.load(config_file)
            assert existing_config == config
        self.patcher_getcwd.start()

    def test_init_project_another_exception(self):
        self.patcher_getcwd.stop()
        with self.runner.isolated_filesystem():
            patcher_mkdir = patch("awsscripter.cli.init1.os.mkdir")
            mock_mkdir = patcher_mkdir.start()
            mock_mkdir.side_effect = OSError(errno.EINVAL)
            result = self.runner.invoke(cli, ["init1", "project", "example"])
            mock_mkdir = patcher_mkdir.stop()
            assert str(result.exception) == str(OSError(errno.EINVAL))
        self.patcher_getcwd.start()

    @pytest.mark.parametrize("environment,config_structure,stdin,result", [
        (
         "A",
         {"": {}},
         'y\nA\nA\n', {"project_code": "A", "region": "A"}
        ),
        (
         "A",
         {"": {"project_code": "top", "region": "top"}},
         'y\n\n\n', {}
        ),
        (
         "A",
         {"": {"project_code": "top", "region": "top"}},
         'y\nA\nA\n', {"project_code": "A", "region": "A"}
        ),
        (
         "A/A",
         {
            "": {"project_code": "top", "region": "top"},
            "A": {"project_code": "A", "region": "A"},
         },
         'y\nA/A\nA/A\n', {"project_code": "A/A", "region": "A/A"}
        ),
        (
         "A/A",
         {
            "": {"project_code": "top", "region": "top"},
            "A": {"project_code": "A", "region": "A"},
         },
         'y\nA\nA\n', {}
        )
    ])
    def test_init_environment(
        self, environment, config_structure, stdin, result
    ):
        self.patcher_getcwd.stop()
        with self.runner.isolated_filesystem():
            sceptre_dir = os.path.abspath('./example')
            config_dir = os.path.join(sceptre_dir, "config")
            os.makedirs(config_dir)

            env_dir = os.path.join(sceptre_dir, "config", environment)
            for env_path, config in config_structure.items():
                path = os.path.join(config_dir, env_path)
                try:
                    os.makedirs(path)
                except OSError as e:
                    if e.errno == errno.EEXIST and os.path.isdir(path):
                        pass
                    else:
                        raise

                filepath = os.path.join(path, "config.yaml")
                with open(filepath, 'w') as config_file:
                    yaml.safe_dump(
                        config, stream=config_file, default_flow_style=False
                    )

            os.chdir(sceptre_dir)

            cmd_result = self.runner.invoke(
                cli, ["init1", "env", environment],
                input=stdin
            )

            if result:
                with open(os.path.join(env_dir, "config.yaml")) as config_file:
                    config = yaml.load(config_file)
                assert config == result
            else:
                assert cmd_result.output.endswith(
                    "No config.yaml file needed - covered by parent config.\n"
                )
        self.patcher_getcwd.start()

    def test_init_environment_with_existing_folder(self):
        self.patcher_getcwd.stop()
        with self.runner.isolated_filesystem():
            sceptre_dir = os.path.abspath('./example')
            config_dir = os.path.join(sceptre_dir, "config")
            env_dir = os.path.join(config_dir, "A")

            os.makedirs(env_dir)
            os.chdir(sceptre_dir)

            cmd_result = self.runner.invoke(
                cli, ["init1", "env", "A"], input="y\n\n\n"
            )

            assert cmd_result.output.startswith(
                "Environment path exists. "
                "Do you want initialise config.yaml?"
            )
            with open(os.path.join(env_dir, "config.yaml")) as config_file:
                config = yaml.load(config_file)
            assert config == {"project_code": "", "region": ""}

        self.patcher_getcwd.start()

    def test_init_environment_with_another_exception(self):
        self.patcher_getcwd.stop()
        with self.runner.isolated_filesystem():
            sceptre_dir = os.path.abspath('./example')
            config_dir = os.path.join(sceptre_dir, "config")
            env_dir = os.path.join(config_dir, "A")

            os.makedirs(env_dir)
            os.chdir(sceptre_dir)

            patcher_mkdir = patch("awsscripter.cli.init1.os.mkdir")
            mock_mkdir = patcher_mkdir.start()
            mock_mkdir.side_effect = OSError(errno.EINVAL)
            result = self.runner.invoke(cli, ["init1", "env", "A"])
            mock_mkdir = patcher_mkdir.stop()
            assert str(result.exception) == str(OSError(errno.EINVAL))

        self.patcher_getcwd.start()

    def test_setup_logging_with_debug(self):
        logger = setup_logging(True, False)
        assert logger.getEffectiveLevel() == logging.DEBUG
        assert logging.getLogger("botocore").getEffectiveLevel() == \
            logging.INFO

        # Silence logging for the rest of the tests
        logger.setLevel(logging.CRITICAL)

    def test_setup_logging_without_debug(self):
        logger = setup_logging(False, False)
        assert logger.getEffectiveLevel() == logging.INFO
        assert logging.getLogger("botocore").getEffectiveLevel() == \
            logging.CRITICAL

        # Silence logging for the rest of the tests
        logger.setLevel(logging.CRITICAL)

    @patch("awsscripter.cli.click.echo")
    def test_write_with_yaml_format(self, mock_echo):
        write({"key": "value"}, "yaml")
        mock_echo.assert_called_once_with("key: value\n")

    @patch("awsscripter.cli.click.echo")
    def test_write_with_json_format(self, mock_echo):
        write({"key": "value"}, "json")
        mock_echo.assert_called_once_with('{"key": "value"}')

    @patch("awsscripter.cli.click.echo")
    def test_write_status_with_colour(self, mock_echo):
        write("stack: CREATE_COMPLETE", no_colour=False)
        mock_echo.assert_called_once_with(
            "stack: \x1b[32mCREATE_COMPLETE\x1b[0m"
        )

    @patch("awsscripter.cli.click.echo")
    def test_write_status_without_colour(self, mock_echo):
        write("stack: CREATE_COMPLETE", no_colour=True)
        mock_echo.assert_called_once_with("stack: CREATE_COMPLETE")

    @patch("awsscripter.cli.helpers.StackStatusColourer.colour")
    @patch("awsscripter.cli.helpers.logging.Formatter.format")
    def test_ColouredFormatter_format_with_string(
            self, mock_format, mock_colour
    ):
        mock_format.return_value = sentinel.response
        mock_colour.return_value = sentinel.coloured_response
        coloured_formatter = ColouredFormatter()
        response = coloured_formatter.format("string")
        mock_format.assert_called_once_with("string")
        mock_colour.assert_called_once_with(sentinel.response)
        assert response == sentinel.coloured_response

    def test_CustomJsonEncoder_with_non_json_serialisable_object(self):
        encoder = CustomJsonEncoder()
        response = encoder.encode(datetime.datetime(2016, 5, 3))
        assert response == '"2016-05-03 00:00:00"'

    def test_get_stack_or_env_with_stack(self):
        ctx = MagicMock(obj={
            "sceptre_dir": sentinel.sceptre_dir,
            "user_variables": sentinel.user_variables
        })
        stack, env = get_stack_or_env(ctx, "stack.yaml")
        self.mock_ConfigReader.assert_called_once_with(
            sentinel.sceptre_dir, sentinel.user_variables
        )
        assert isinstance(stack, Stack)
        assert env is None

    def test_get_stack_or_env_with_nested_stack(self):
        ctx = MagicMock(obj={
            "sceptre_dir": sentinel.sceptre_dir,
            "user_variables": sentinel.user_variables
        })
        stack, env = get_stack_or_env(ctx, "environment/dir/stack.yaml")
        self.mock_ConfigReader.assert_called_once_with(
            sentinel.sceptre_dir, sentinel.user_variables
        )
        assert isinstance(stack, Stack)
        assert env is None

    def test_get_stack_or_env_with_env(self):
        ctx = MagicMock(obj={
            "sceptre_dir": sentinel.sceptre_dir,
            "user_variables": sentinel.user_variables
        })
        stack, env = get_stack_or_env(ctx, "environment/dir")
        self.mock_ConfigReader.assert_called_once_with(
            sentinel.sceptre_dir, sentinel.user_variables
        )
        assert isinstance(env, Environment)
        assert stack is None
def main():
    foo = TestCli()  
    # foo.setup_method("test_list_stack_resources")
    # foo.test_list_stack_resources()
    foo.setup_method("test_create_envorstack")
    foo.test_create_envorstack()


if __name__ == "__main__":
    main()