Python 3.x Boto3列表_stacks()中带有Moto的Pytest cov

Python 3.x Boto3列表_stacks()中带有Moto的Pytest cov,python-3.x,pytest,boto3,moto,pytest-cov,Python 3.x,Pytest,Boto3,Moto,Pytest Cov,我正在尝试使用pytest和moto创建一个测试,检查从函数list\u stacks()()返回的字典中的StackStatus是否为DELETE\u COMPLETE 我为客户端连接创建pytest.fixture: @pytest.fixture(scope='function') def cf(aws_credentials): with mock_cloudformation(): yield boto3.client('cloudformation') 我创

我正在尝试使用pytest和moto创建一个测试,检查从函数
list\u stacks()
()返回的字典中的
StackStatus
是否为
DELETE\u COMPLETE

我为客户端连接创建pytest.fixture:

@pytest.fixture(scope='function')
def cf(aws_credentials):
    with mock_cloudformation():
        yield boto3.client('cloudformation')
我创建了一个假人模板:

@pytest.fixture(scope='function')
def template_body_data():
    'The Cloud Formation template'
    template_data = {
        'Resources': {'MyS3Bucket': {'Type': 'AWS::S3::Bucket', 'Properties': {}}}
    }
    return template_data
在我的测试函数中,我创建了堆栈,删除后:

@mock_cloudformation
def test_deleted_stack(cf, template_body_data):
    params = {'StackName': 'teste', 'TemplateBody': yaml.dump(template_body_data)}
    cf.create_stack(**params)
    cf.delete_stack(StackName='teste')
    assert check_stack_exists('teste') is False
正在测试的功能是:

def check_stack_exists(stack_name):
    cf = get_client()
    list_stack = cf.list_stacks()['StackSummaries']
    for stack in list_stack:
        if stack['StackStatus'] == 'DELETE_COMPLETE':
            continue
        if stack['StackName'] == stack_name:
            return True
    return False
我面临一个问题:

它抱怨我的模板没有BucketName:


___________________ test_check_stack_exists_deleted_stack ___________________

cf = <botocore.client.CloudFormation object at 0x7f59ca68f580>
template_body_data = {'Resources': {'MyS3Bucket': {'Properties': {}, 'Type': 'AWS::S3::Bucket'}}}

    @mock_cloudformation
    def test_check_stack_exists_deleted_stack(cf, template_body_data):
        'Test the stack search if the stack exists'
        params = {'StackName': 'teste', 'TemplateBody': yaml.dump(template_body_data)}
        cf.create_stack(**params)
>       cf.delete_stack(StackName='teste')

tests/test_cl_uploader.py:87: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/client.py:316: in _api_call
    return self._make_api_call(operation_name, kwargs)
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/client.py:621: in _make_api_call
    http, parsed_response = self._make_request(
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/client.py:641: in _make_request
    return self._endpoint.make_request(operation_model, request_dict)
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/endpoint.py:102: in make_request
    return self._send_request(request_dict, operation_model)
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/endpoint.py:136: in _send_request
    while self._needs_retry(attempts, operation_model, request_dict,
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/endpoint.py:253: in _needs_retry
    responses = self._event_emitter.emit(
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/hooks.py:356: in emit
    return self._emitter.emit(aliased_event_name, **kwargs)
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/hooks.py:228: in emit
    return self._emit(event_name, kwargs)
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/hooks.py:211: in _emit
    response = handler(**kwargs)
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/retryhandler.py:183: in __call__
    if self._checker(attempts, response, caught_exception):
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/retryhandler.py:250: in __call__
    should_retry = self._should_retry(attempt_number, response,
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/retryhandler.py:269: in _should_retry
    return self._checker(attempt_number, response, caught_exception)
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/retryhandler.py:316: in __call__
    checker_response = checker(attempt_number, response,
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/retryhandler.py:222: in __call__
    return self._check_caught_exception(
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/retryhandler.py:359: in _check_caught_exception
    raise caught_exception
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/endpoint.py:197: in _do_get_response
    responses = self._event_emitter.emit(event_name, request=request)
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/hooks.py:356: in emit
    return self._emitter.emit(aliased_event_name, **kwargs)
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/hooks.py:228: in emit
    return self._emit(event_name, kwargs)
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/hooks.py:211: in _emit
    response = handler(**kwargs)
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/moto/core/models.py:322: in __call__
    status, headers, body = response_callback(
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/moto/core/responses.py:202: in dispatch
    return cls()._dispatch(*args, **kwargs)
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/moto/core/responses.py:312: in _dispatch
    return self.call_action()
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/moto/core/responses.py:397: in call_action
    response = method()
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/moto/cloudformation/responses.py:380: in delete_stack
    self.cloudformation_backend.delete_stack(name_or_stack_id)
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/moto/cloudformation/models.py:734: in delete_stack
    self.delete_stack(stack.stack_id)
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/moto/cloudformation/models.py:726: in delete_stack
    stack.delete()
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/moto/cloudformation/models.py:363: in delete
    self.resource_map.delete()
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/moto/cloudformation/parsing.py:677: in delete
    raise last_exception
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <moto.cloudformation.parsing.ResourceMap object at 0x7f59cabd3940>

    def delete(self):
        remaining_resources = set(self.resources)
        tries = 1
        while remaining_resources and tries < 5:
            for resource in remaining_resources.copy():
                parsed_resource = self._parsed_resources.get(resource)
                try:
                    if parsed_resource and hasattr(parsed_resource, "delete"):
                        parsed_resource.delete(self._region_name)
                    else:
                        resource_name_attribute = (
                            parsed_resource.cloudformation_name_type()
                            if hasattr(parsed_resource, "cloudformation_name_type")
                            else resource_name_property_from_type(parsed_resource.type)
                        )
                        if resource_name_attribute:
                            resource_json = self._resource_json_map[
                                parsed_resource.logical_resource_id
                            ]
>                           resource_name = resource_json["Properties"][
                                resource_name_attribute
                            ]
E                           KeyError: 'BucketName'

../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/moto/cloudformation/parsing.py:662: KeyError

___________________测试\u检查\u堆栈\u存在\u已删除\u堆栈___________________
cf=
模板_body_data={'Resources':{'MyS3Bucket':{'Properties':{},'Type':'AWS::S3::Bucket'}}
@模拟云层
def测试\检查\堆栈\存在\删除\堆栈(cf,模板\主体\数据):
'如果堆栈存在,则测试堆栈搜索'
params={'StackName':'teste','TemplateBody':yaml.dump(template_body_data)}
cf.create_堆栈(**参数)
>cf.delete_堆栈(StackName='teste')
测试/测试cl_上传器。py:87:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../...cache/pypoety/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/botocore/client.py:316:in(api)调用
返回self.\u make\u api\u调用(操作名称,kwargs)
../...cache/pypoety/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/botocore/client.py:621:in(生成)api(调用)
http,已解析的\u响应=self.\u发出\u请求(
../...cache/pypoety/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/botocore/client.py:641:in(发出请求)
返回self.\u endpoint.make\u请求(操作\u模型,请求\u dict)
../...cache/pypoety/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/botocore/endpoint.py:102:在make\u请求中
返回自我。发送请求(请求指令、操作模式)
../...cache/pypoyment/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/botocore/endpoint.py:136:in(发送)请求
而self.\u需要重试(尝试、操作模式、请求命令、,
../...cache/pypoyment/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/botocore/endpoint.py:253:in需要重试
响应=自。\事件\发射器发射(
../...cache/pypoety/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/botocore/hooks.py:356:in-emit
返回self.\u emitter.emit(别名为\u事件\u名称,**kwargs)
../...cache/pypoyment/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/botocore/hooks.py:228:in-emit
返回自发出(事件名称,kwargs)
../...cache/pypoyment/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/botocore/hooks.py:211:in\u
响应=处理程序(**kwargs)
../...cache/pypoyment/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/botocore/retryhandler.py:183:in\uu调用__
如果自我检查(尝试、响应、捕获异常):
../...cache/pypoyment/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/botocore/retryhandler.py:250:in\uu调用__
should\u retry=self.\u should\u retry(尝试次数、响应、,
../...cache/pypoyment/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/botocore/retryhandler.py:269:in(应该重试)
返回自我检查(尝试编号、响应、捕获异常)
../...cache/pypoyment/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/botocore/retryhandler.py:316:in\uu调用__
checker\u response=checker(尝试次数、响应、,
../...cache/pypoety/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/botocore/retryhandler.py:222:in\uu调用__
返回自我。\u检查\u捕获\u异常(
../...cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/botocore/retryhandler.py:359:in(检查)捕获(异常)
引发被捕获的异常
../../.cache/pypoyment/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/botocore/endpoint.py:197:in(do)get(u response)
responses=self.\u event\u emitter.emit(事件名称,请求=request)
../...cache/pypoety/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/botocore/hooks.py:356:in-emit
返回self.\u emitter.emit(别名为\u事件\u名称,**kwargs)
../...cache/pypoyment/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/botocore/hooks.py:228:in-emit
返回自发出(事件名称,kwargs)
../...cache/pypoyment/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/botocore/hooks.py:211:in\u
响应=处理程序(**kwargs)
../...cache/pypoety/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/moto/core/models.py:322:in\u调用__
状态、标题、正文=响应\回调(
../...cache/pypoety/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/moto/core/responses.py:202:正在发送
返回cls()。_调度(*args,**kwargs)
../...cache/pypoety/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/moto/core/responses.py:312:in
返回self.call_action()
../...cache/pypoety/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/moto/core/responses.py:397:调用中
响应=方法()
../...cache/pypoety/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/moto/cloudformation/responses.py:380:在delete_堆栈中
self.cloudformation\u backend.delete\u stack(name\u或\u stack\u id)
../...cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/moto/cloudformation/models.py:734:在delete_堆栈中
self.delete\u堆栈(stack.stack\u id)
../...cache/pypoety/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/moto/cloudformation/models.py:726:在delete_堆栈中
stack.delete()
../...cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site packages/moto/cloudformation/models.py:363:删除中
self.resource\u map.delete(
================================= FAILURES ==================================
___________________ test_check_stack_exists_deleted_stack ___________________

cf = <botocore.client.CloudFormation object at 0x7f7a02ca4ee0>
template_body_data = {'Resources': {'MyS3Bucket': {'Properties': {'BucketName': 'teste'}, 'Type': 'AWS::S3::Bucket'}}}

    @mock_cloudformation
    def test_check_stack_exists_deleted_stack(cf, template_body_data):
        'Test the stack search if the stack exists'
        params = {'StackName': 'teste', 'TemplateBody': yaml.dump(template_body_data)}
>       cf.create_stack(**params)

tests/test_cl_uploader.py:86: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/client.py:316: in _api_call
    return self._make_api_call(operation_name, kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <botocore.client.CloudFormation object at 0x7f7a02ca4ee0>
operation_name = 'CreateStack'
api_params = {'StackName': 'teste', 'TemplateBody': 'Resources:\n  MyS3Bucket:\n    Properties:\n      BucketName: teste\n    Type: AWS::S3::Bucket\n'}

    def _make_api_call(self, operation_name, api_params):
        operation_model = self._service_model.operation_model(operation_name)
        service_name = self._service_model.service_name
        history_recorder.record('API_CALL', {
            'service': service_name,
            'operation': operation_name,
            'params': api_params,
        })
        if operation_model.deprecated:
            logger.debug('Warning: %s.%s() is deprecated',
                         service_name, operation_name)
        request_context = {
            'client_region': self.meta.region_name,
            'client_config': self.meta.config,
            'has_streaming_input': operation_model.has_streaming_input,
            'auth_type': operation_model.auth_type,
        }
        request_dict = self._convert_to_request_dict(
            api_params, operation_model, context=request_context)
    
        service_id = self._service_model.service_id.hyphenize()
        handler, event_response = self.meta.events.emit_until_response(
            'before-call.{service_id}.{operation_name}'.format(
                service_id=service_id,
                operation_name=operation_name),
            model=operation_model, params=request_dict,
            request_signer=self._request_signer, context=request_context)
    
        if event_response is not None:
            http, parsed_response = event_response
        else:
            http, parsed_response = self._make_request(
                operation_model, request_dict, request_context)
    
        self.meta.events.emit(
            'after-call.{service_id}.{operation_name}'.format(
                service_id=service_id,
                operation_name=operation_name),
            http_response=http, parsed=parsed_response,
            model=operation_model, context=request_context
        )
    
        if http.status_code >= 300:
            error_code = parsed_response.get("Error", {}).get("Code")
            error_class = self.exceptions.from_code(error_code)
>           raise error_class(parsed_response, operation_name)
E           botocore.exceptions.ClientError: An error occurred (Unknown) when calling the CreateStack operation: Unknown

../../.cache/pypoetry/virtualenvs/cl-uploader-12nYBdPj-py3.8/lib/python3.8/site-packages/botocore/client.py:635: ClientError