1import datetime
2import logging
3import os
4import random
5import string
6
7import pytest
8import salt.config
9import salt.loader
10import salt.states.boto_apigateway as boto_apigateway
11import salt.utils.files
12import salt.utils.yaml
13from salt.utils.versions import LooseVersion
14from tests.support.mixins import LoaderModuleMockMixin
15from tests.support.mock import MagicMock, patch
16from tests.support.unit import TestCase, skipIf
17
18# pylint: disable=import-error,no-name-in-module
19from tests.unit.modules.test_boto_apigateway import BotoApiGatewayTestCaseMixin
20
21try:
22    import boto3
23    import botocore
24    from botocore.exceptions import ClientError
25
26    HAS_BOTO = True
27except ImportError:
28    HAS_BOTO = False
29
30
31# pylint: enable=import-error,no-name-in-module
32
33# the boto_apigateway module relies on the connect_to_region() method
34# which was added in boto 2.8.0
35# https://github.com/boto/boto/commit/33ac26b416fbb48a60602542b4ce15dcc7029f12
36required_boto3_version = "1.2.1"
37required_botocore_version = "1.4.49"
38
39region = "us-east-1"
40access_key = "GKTADJGHEIQSXMKKRBJ08H"
41secret_key = "askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs"
42conn_parameters = {
43    "region": region,
44    "key": access_key,
45    "keyid": secret_key,
46    "profile": {},
47}
48error_message = (
49    "An error occurred (101) when calling the {0} operation: Test-defined error"
50)
51error_content = {"Error": {"Code": 101, "Message": "Test-defined error"}}
52
53api_ret = dict(
54    description=(
55        '{\n    "context": "See deployment or stage description",\n   '
56        ' "provisioned_by": "Salt boto_apigateway.present State"\n}'
57    ),
58    createdDate=datetime.datetime(2015, 11, 17, 16, 33, 50),
59    id="vni0vq8wzi",
60    name="unit test api",
61)
62
63no_apis_ret = {"items": []}
64
65apis_ret = {"items": [api_ret]}
66
67mock_model_ret = dict(
68    contentType="application/json",
69    description="mock model",
70    id="123abc",
71    name="mock model",
72    schema=(
73        "{\n"
74        '    "$schema": "http://json-schema.org/draft-04/schema#",\n'
75        '    "properties": {\n'
76        '        "field": {\n'
77        '            "type": "string"\n'
78        "        }\n"
79        "    }\n"
80        "}"
81    ),
82)
83
84models_ret = {
85    "items": [
86        dict(
87            contentType="application/json",
88            description="Error",
89            id="50nw8r",
90            name="Error",
91            schema=(
92                "{\n"
93                '    "$schema": "http://json-schema.org/draft-04/schema#",\n'
94                '    "properties": {\n'
95                '        "code": {\n'
96                '            "format": "int32",\n'
97                '            "type": "integer"\n'
98                "        },\n"
99                '        "fields": {\n'
100                '            "type": "string"\n'
101                "        },\n"
102                '        "message": {\n'
103                '            "type": "string"\n'
104                "        }\n"
105                "    },\n"
106                '    "title": "Error Schema",\n'
107                '    "type": "object"\n'
108                "}"
109            ),
110        ),
111        dict(
112            contentType="application/json",
113            description="User",
114            id="terlnw",
115            name="User",
116            schema=(
117                "{\n"
118                '    "$schema": "http://json-schema.org/draft-04/schema#",\n'
119                '    "properties": {\n'
120                '        "password": {\n'
121                '            "description": "A password for the new user",\n'
122                '            "type": "string"\n'
123                "        },\n"
124                '        "username": {\n'
125                '            "description": "A unique username for the user",\n'
126                '            "type": "string"\n'
127                "        }\n"
128                "    },\n"
129                '    "title": "User Schema",\n'
130                '    "type": "object"\n'
131                "}"
132            ),
133        ),
134    ]
135}
136
137root_resources_ret = {"items": [dict(id="bgk0rk8rqb", path="/")]}
138
139resources_ret = {
140    "items": [
141        dict(id="bgk0rk8rqb", path="/"),
142        dict(
143            id="9waiaz",
144            parentId="bgk0rk8rqb",
145            path="/users",
146            pathPart="users",
147            resourceMethods={"POST": {}},
148        ),
149    ]
150}
151
152no_resources_ret = {"items": []}
153
154stage1_deployment1_ret = dict(
155    cacheClusterEnabled=False,
156    cacheClusterSize=0.5,
157    cacheClusterStatus="NOT_AVAILABLE",
158    createdDate=datetime.datetime(2015, 11, 17, 16, 33, 50),
159    deploymentId="kobnrb",
160    description=(
161        "{\n"
162        '    "current_deployment_label": {\n'
163        '        "api_name": "unit test api",\n'
164        '        "swagger_file": "temp-swagger-sample.yaml",\n'
165        '        "swagger_file_md5sum": "4fb17e43bab3a96e7f2410a1597cd0a5",\n'
166        '        "swagger_info_object": {\n'
167        '            "description": "salt boto apigateway unit test service",\n'
168        '            "title": "salt boto apigateway unit test service",\n'
169        '            "version": "0.0.0"\n'
170        "        }\n"
171        "    }\n"
172        "}"
173    ),
174    lastUpdatedDate=datetime.datetime(2015, 11, 17, 16, 33, 50),
175    methodSettings=dict(),
176    stageName="test",
177    variables=dict(),
178)
179
180stage1_deployment1_vars_ret = dict(
181    cacheClusterEnabled=False,
182    cacheClusterSize=0.5,
183    cacheClusterStatus="NOT_AVAILABLE",
184    createdDate=datetime.datetime(2015, 11, 17, 16, 33, 50),
185    deploymentId="kobnrb",
186    description=(
187        "{\n"
188        '    "current_deployment_label": {\n'
189        '        "api_name": "unit test api",\n'
190        '        "swagger_file": "temp-swagger-sample.yaml",\n'
191        '        "swagger_file_md5sum": "4fb17e43bab3a96e7f2410a1597cd0a5",\n'
192        '        "swagger_info_object": {\n'
193        '            "description": "salt boto apigateway unit test service",\n'
194        '            "title": "salt boto apigateway unit test service",\n'
195        '            "version": "0.0.0"\n'
196        "        }\n"
197        "    }\n"
198        "}"
199    ),
200    lastUpdatedDate=datetime.datetime(2015, 11, 17, 16, 33, 50),
201    methodSettings=dict(),
202    stageName="test",
203    variables={"var1": "val1"},
204)
205
206stage1_deployment2_ret = dict(
207    cacheClusterEnabled=False,
208    cacheClusterSize=0.5,
209    cacheClusterStatus="NOT_AVAILABLE",
210    createdDate=datetime.datetime(2015, 11, 17, 16, 33, 50),
211    deploymentId="kobnrc",
212    description=(
213        "{\n"
214        '    "current_deployment_label": {\n'
215        '        "api_name": "unit test api",\n'
216        '        "swagger_file": "temp-swagger-sample.yaml",\n'
217        '        "swagger_file_md5sum": "5fd538c4336ed5c54b4bf39ddf97c661",\n'
218        '        "swagger_info_object": {\n'
219        '            "description": "salt boto apigateway unit test service",\n'
220        '            "title": "salt boto apigateway unit test service",\n'
221        '            "version": "0.0.2"\n'
222        "        }\n"
223        "    }\n"
224        "}"
225    ),
226    lastUpdatedDate=datetime.datetime(2015, 11, 17, 16, 33, 50),
227    methodSettings=dict(),
228    stageName="test",
229    variables=dict(),
230)
231
232stage2_ret = dict(
233    cacheClusterEnabled=False,
234    cacheClusterSize=0.5,
235    cacheClusterStatus="NOT_AVAILABLE",
236    createdDate=datetime.datetime(2015, 11, 17, 16, 33, 50),
237    deploymentId="kobnrb",
238    description=(
239        "{\n"
240        '    "current_deployment_label": {\n'
241        '        "api_name": "unit test api",\n'
242        '        "swagger_file": "temp-swagger-sample.yaml",\n'
243        '        "swagger_file_md5sum": "4fb17e43bab3a96e7f2410a1597cd0a5",\n'
244        '        "swagger_info_object": {\n'
245        '            "description": "salt boto apigateway unit test service",\n'
246        '            "title": "salt boto apigateway unit test service",\n'
247        '            "version": "0.0.0"\n'
248        "        }\n"
249        "    }\n"
250        "}"
251    ),
252    lastUpdatedDate=datetime.datetime(2015, 11, 17, 16, 33, 50),
253    methodSettings=dict(),
254    stageName="dev",
255    variables=dict(),
256)
257
258stages_stage2_ret = {"item": [stage2_ret]}
259
260no_stages_ret = {"item": []}
261
262deployment1_ret = dict(
263    createdDate=datetime.datetime(2015, 11, 17, 16, 33, 50),
264    description=(
265        "{\n"
266        '    "api_name": "unit test api",\n'
267        '    "swagger_file": "temp-swagger-sample.yaml",\n'
268        '    "swagger_file_md5sum": "55a948ff90ad80ff747ec91657c7a299",\n'
269        '    "swagger_info_object": {\n'
270        '        "description": "salt boto apigateway unit test service",\n'
271        '        "title": "salt boto apigateway unit test service",\n'
272        '        "version": "0.0.0"\n'
273        "    }\n"
274        "}"
275    ),
276    id="kobnrb",
277)
278
279deployment2_ret = dict(
280    createdDate=datetime.datetime(2015, 11, 17, 16, 33, 50),
281    description=(
282        "{\n"
283        '    "api_name": "unit test api",\n'
284        '    "swagger_file": "temp-swagger-sample.yaml",\n'
285        '    "swagger_file_md5sum": "5fd538c4336ed5c54b4bf39ddf97c661",\n'
286        '    "swagger_info_object": {\n'
287        '        "description": "salt boto apigateway unit test service",\n'
288        '        "title": "salt boto apigateway unit test service",\n'
289        '        "version": "0.0.2"\n'
290        "    }\n"
291        "}"
292    ),
293    id="kobnrc",
294)
295
296deployments_ret = {"items": [deployment1_ret, deployment2_ret]}
297
298function_ret = dict(
299    FunctionName="unit_test_api_users_post",
300    Runtime="python2.7",
301    Role=None,
302    Handler="handler",
303    Description="abcdefg",
304    Timeout=5,
305    MemorySize=128,
306    CodeSha256="abcdef",
307    CodeSize=199,
308    FunctionArn="arn:lambda:us-east-1:1234:Something",
309    LastModified="yes",
310)
311
312method_integration_response_200_ret = dict(
313    responseParameters={"method.response.header.Access-Control-Allow-Origin": "*"},
314    responseTemplates={},
315    selectionPattern=".*",
316    statusCode="200",
317)
318
319method_integration_ret = dict(
320    cacheKeyParameters={},
321    cacheNamespace="9waiaz",
322    credentials="arn:aws:iam::1234:role/apigatewayrole",
323    httpMethod="POST",
324    integrationResponses={"200": method_integration_response_200_ret},
325    requestParameters={},
326    requestTemplates={
327        "application/json": (
328            "#set($inputRoot = $input.path('$')){\"header-params\" : {#set ($map ="
329            ' $input.params().header)#foreach( $param in $map.entrySet() )"$param.key"'
330            ' : "$param.value" #if( $foreach.hasNext ), #end#end},"query-params" :'
331            " {#set ($map = $input.params().querystring)#foreach( $param in"
332            ' $map.entrySet() )"$param.key" : "$param.value" #if( $foreach.hasNext ),'
333            ' #end#end},"path-params" : {#set ($map = $input.params().path)#foreach('
334            ' $param in $map.entrySet() )"$param.key" : "$param.value" #if('
335            " $foreach.hasNext ), #end#end},\"body-params\" : $input.json('$')}"
336        )
337    },
338    type="AWS",
339    uri=(
340        "arn:aws:apigateway:us-west-2:"
341        "lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-2:1234567:"
342        "function:unit_test_api_api_users_post/invocations"
343    ),
344)
345
346method_response_200_ret = dict(
347    responseModels={"application/json": "User"},
348    responseParameters={"method.response.header.Access-Control-Allow-Origin": False},
349    statusCode="200",
350)
351
352method_ret = dict(
353    apiKeyRequired=False,
354    authorizationType="None",
355    httpMethod="POST",
356    methodIntegration=method_integration_ret,
357    methodResponses={"200": method_response_200_ret},
358    requestModels={"application/json": "User"},
359    requestParameters={},
360)
361
362throttle_rateLimit = 10.0
363association_stage_1 = {"apiId": "apiId1", "stage": "stage1"}
364association_stage_2 = {"apiId": "apiId1", "stage": "stage2"}
365
366log = logging.getLogger(__name__)
367
368
369def _has_required_boto():
370    """
371    Returns True/False boolean depending on if Boto is installed and correct
372    version.
373    """
374    if not HAS_BOTO:
375        return False
376    elif LooseVersion(boto3.__version__) < LooseVersion(required_boto3_version):
377        return False
378    else:
379        return True
380
381
382def _has_required_botocore():
383    """
384    Returns True/False boolean depending on if botocore supports usage plan
385    """
386    if not HAS_BOTO:
387        return False
388    elif LooseVersion(botocore.__version__) < LooseVersion(required_botocore_version):
389        return False
390    else:
391        return True
392
393
394class TempSwaggerFile:
395    _tmp_swagger_dict = {
396        "info": {
397            "version": "0.0.0",
398            "description": "salt boto apigateway unit test service",
399            "title": "salt boto apigateway unit test service",
400        },
401        "paths": {
402            "/users": {
403                "post": {
404                    "responses": {
405                        "200": {
406                            "headers": {
407                                "Access-Control-Allow-Origin": {"type": "string"}
408                            },
409                            "description": "The username of the new user",
410                            "schema": {"$ref": "#/definitions/User"},
411                        }
412                    },
413                    "parameters": [
414                        {
415                            "in": "body",
416                            "description": "New user details.",
417                            "name": "NewUser",
418                            "schema": {"$ref": "#/definitions/User"},
419                        }
420                    ],
421                    "produces": ["application/json"],
422                    "description": "Creates a new user.",
423                    "tags": ["Auth"],
424                    "consumes": ["application/json"],
425                    "summary": "Registers a new user",
426                }
427            }
428        },
429        "schemes": ["https"],
430        "produces": ["application/json"],
431        "basePath": "/api",
432        "host": "rm06h9oac4.execute-api.us-west-2.amazonaws.com",
433        "definitions": {
434            "User": {
435                "properties": {
436                    "username": {
437                        "type": "string",
438                        "description": "A unique username for the user",
439                    },
440                    "password": {
441                        "type": "string",
442                        "description": "A password for the new user",
443                    },
444                }
445            },
446            "Error": {
447                "properties": {
448                    "fields": {"type": "string"},
449                    "message": {"type": "string"},
450                    "code": {"type": "integer", "format": "int32"},
451                }
452            },
453        },
454        "swagger": "2.0",
455    }
456
457    def __enter__(self):
458        self.swaggerfile = "temp-swagger-sample.yaml"
459        with salt.utils.files.fopen(self.swaggerfile, "w") as fp_:
460            salt.utils.yaml.safe_dump(self.swaggerdict, fp_, default_flow_style=False)
461        return self.swaggerfile
462
463    def __exit__(self, objtype, value, traceback):
464        os.remove(self.swaggerfile)
465
466    def __init__(self, create_invalid_file=False):
467        if create_invalid_file:
468            self.swaggerdict = TempSwaggerFile._tmp_swagger_dict.copy()
469            # add an invalid top level key
470            self.swaggerdict["invalid_key"] = "invalid"
471            # remove one of the required keys 'schemes'
472            self.swaggerdict.pop("schemes", None)
473            # set swagger version to an unsupported version 3.0
474            self.swaggerdict["swagger"] = "3.0"
475            # missing info object
476            self.swaggerdict.pop("info", None)
477        else:
478            self.swaggerdict = TempSwaggerFile._tmp_swagger_dict
479
480
481class BotoApiGatewayStateTestCaseBase(TestCase, LoaderModuleMockMixin):
482    conn = None
483
484    @classmethod
485    def setUpClass(cls):
486        cls.opts = salt.config.DEFAULT_MINION_OPTS.copy()
487        cls.opts["grains"] = salt.loader.grains(cls.opts)
488
489    @classmethod
490    def tearDownClass(cls):
491        del cls.opts
492
493    def setup_loader_modules(self):
494        context = {}
495        utils = salt.loader.utils(
496            self.opts,
497            whitelist=["boto", "boto3", "args", "systemd", "path", "platform", "reg"],
498            context=context,
499        )
500        serializers = salt.loader.serializers(self.opts)
501        self.funcs = salt.loader.minion_mods(
502            self.opts, context=context, utils=utils, whitelist=["boto_apigateway"]
503        )
504        self.salt_states = salt.loader.states(
505            opts=self.opts,
506            functions=self.funcs,
507            utils=utils,
508            whitelist=["boto_apigateway"],
509            serializers=serializers,
510        )
511        return {
512            boto_apigateway: {
513                "__opts__": self.opts,
514                "__utils__": utils,
515                "__salt__": self.funcs,
516                "__states__": self.salt_states,
517                "__serializers__": serializers,
518            }
519        }
520
521    # Set up MagicMock to replace the boto3 session
522    def setUp(self):
523        self.addCleanup(delattr, self, "funcs")
524        self.addCleanup(delattr, self, "salt_states")
525        # connections keep getting cached from prior tests, can't find the
526        # correct context object to clear it. So randomize the cache key, to prevent any
527        # cache hits
528        conn_parameters["key"] = "".join(
529            random.choice(string.ascii_lowercase + string.digits) for _ in range(50)
530        )
531
532        patcher = patch("boto3.session.Session")
533        self.addCleanup(patcher.stop)
534        mock_session = patcher.start()
535
536        session_instance = mock_session.return_value
537        self.conn = MagicMock()
538        self.addCleanup(delattr, self, "conn")
539        session_instance.client.return_value = self.conn
540
541
542@skipIf(HAS_BOTO is False, "The boto module must be installed.")
543@skipIf(
544    _has_required_boto() is False,
545    "The boto3 module must be greater than or equal to version {}".format(
546        required_boto3_version
547    ),
548)
549class BotoApiGatewayTestCase(
550    BotoApiGatewayStateTestCaseBase, BotoApiGatewayTestCaseMixin
551):
552    """
553    TestCase for salt.modules.boto_apigateway state.module
554    """
555
556    def test_present_when_swagger_file_is_invalid(self):
557        """
558        Tests present when the swagger file is invalid.
559        """
560        result = {}
561        with TempSwaggerFile(create_invalid_file=True) as swagger_file:
562            result = self.salt_states["boto_apigateway.present"](
563                "api present",
564                "unit test api",
565                swagger_file,
566                "test",
567                False,
568                "arn:aws:iam::1234:role/apigatewayrole",
569                **conn_parameters
570            )
571
572        self.assertFalse(result.get("result", True))
573
574    def test_present_when_stage_is_already_at_desired_deployment(self):
575        """
576        Tests scenario where no action will be taken since we're already
577        at desired state
578        """
579        self.conn.get_rest_apis.return_value = apis_ret
580        self.conn.get_deployment.return_value = deployment1_ret
581        self.conn.get_stage.return_value = stage1_deployment1_ret
582        self.conn.update_stage.side_effect = ClientError(
583            error_content, "update_stage should not be called"
584        )
585        result = {}
586        with TempSwaggerFile() as swagger_file:
587            result = self.salt_states["boto_apigateway.present"](
588                "api present",
589                "unit test api",
590                swagger_file,
591                "test",
592                False,
593                "arn:aws:iam::1234:role/apigatewayrole",
594                **conn_parameters
595            )
596        self.assertFalse(result.get("abort"))
597        self.assertTrue(result.get("current"))
598        self.assertIs(result.get("result"), True)
599        self.assertNotIn("update_stage should not be called", result.get("comment", ""))
600
601    def test_present_when_stage_is_already_at_desired_deployment_and_needs_stage_variables_update(
602        self,
603    ):
604        """
605        Tests scenario where the deployment is current except for the need to update stage variables
606        from {} to {'var1':'val1'}
607        """
608        self.conn.get_rest_apis.return_value = apis_ret
609        self.conn.get_deployment.return_value = deployment1_ret
610        self.conn.get_stage.return_value = stage1_deployment1_ret
611        self.conn.update_stage.return_value = stage1_deployment1_vars_ret
612        result = {}
613        with TempSwaggerFile() as swagger_file:
614            result = self.salt_states["boto_apigateway.present"](
615                "api present",
616                "unit test api",
617                swagger_file,
618                "test",
619                False,
620                "arn:aws:iam::1234:role/apigatewayrole",
621                stage_variables={"var1": "val1"},
622                **conn_parameters
623            )
624
625        self.assertFalse(result.get("abort"))
626        self.assertTrue(result.get("current"))
627        self.assertIs(result.get("result"), True)
628
629    def test_present_when_stage_exists_and_is_to_associate_to_existing_deployment(self):
630        """
631        Tests scenario where we merely reassociate a stage to a pre-existing
632        deployments
633        """
634        self.conn.get_rest_apis.return_value = apis_ret
635        self.conn.get_deployment.return_value = deployment2_ret
636        self.conn.get_deployments.return_value = deployments_ret
637        self.conn.get_stage.return_value = stage1_deployment2_ret
638        self.conn.update_stage.return_value = stage1_deployment1_ret
639
640        # should never get to the following calls
641        self.conn.create_stage.side_effect = ClientError(error_content, "create_stage")
642        self.conn.create_deployment.side_effect = ClientError(
643            error_content, "create_deployment"
644        )
645
646        result = {}
647        with TempSwaggerFile() as swagger_file:
648            result = self.salt_states["boto_apigateway.present"](
649                "api present",
650                "unit test api",
651                swagger_file,
652                "test",
653                False,
654                "arn:aws:iam::1234:role/apigatewayrole",
655                **conn_parameters
656            )
657
658        self.assertTrue(result.get("publish"))
659        self.assertIs(result.get("result"), True)
660        self.assertFalse(result.get("abort"))
661        self.assertTrue(result.get("changes", {}).get("new", [{}])[0])
662
663    @pytest.mark.slow_test
664    def test_present_when_stage_is_to_associate_to_new_deployment(self):
665        """
666        Tests creation of a new api/model/resource given nothing has been created previously
667        """
668        # no api existed
669        self.conn.get_rest_apis.return_value = no_apis_ret
670        # create top level api
671        self.conn.create_rest_api.return_value = api_ret
672        # no models existed in the newly created api
673        self.conn.get_model.side_effect = ClientError(error_content, "get_model")
674        # create model return values
675        self.conn.create_model.return_value = mock_model_ret
676        # various paths/resources already created
677        self.conn.get_resources.return_value = resources_ret
678        # the following should never be called
679        self.conn.create_resource.side_effect = ClientError(
680            error_content, "create_resource"
681        )
682
683        # create api method for POST
684        self.conn.put_method.return_value = method_ret
685        # create api method integration for POST
686        self.conn.put_integration.return_value = method_integration_ret
687        # create api method response for POST/200
688        self.conn.put_method_response.return_value = method_response_200_ret
689        # create api method integration response for POST
690        self.conn.put_intgration_response.return_value = (
691            method_integration_response_200_ret
692        )
693
694        result = {}
695        with patch.dict(
696            self.funcs,
697            {
698                "boto_lambda.describe_function": MagicMock(
699                    return_value={"function": function_ret}
700                )
701            },
702        ):
703            with TempSwaggerFile() as swagger_file:
704                result = self.salt_states["boto_apigateway.present"](
705                    "api present",
706                    "unit test api",
707                    swagger_file,
708                    "test",
709                    False,
710                    "arn:aws:iam::1234:role/apigatewayrole",
711                    **conn_parameters
712                )
713
714        self.assertIs(result.get("result"), True)
715        self.assertIs(result.get("abort"), None)
716
717    def test_present_when_stage_associating_to_new_deployment_errored_on_api_creation(
718        self,
719    ):
720        """
721        Tests creation of a new api/model/resource given nothing has been created previously,
722        and we failed on creating the top level api object.
723        """
724        # no api existed
725        self.conn.get_rest_apis.return_value = no_apis_ret
726        # create top level api
727        self.conn.create_rest_api.side_effect = ClientError(
728            error_content, "create_rest_api"
729        )
730
731        result = {}
732        with TempSwaggerFile() as swagger_file:
733            result = self.salt_states["boto_apigateway.present"](
734                "api present",
735                "unit test api",
736                swagger_file,
737                "test",
738                False,
739                "arn:aws:iam::1234:role/apigatewayrole",
740                **conn_parameters
741            )
742
743        self.assertIs(result.get("abort"), True)
744        self.assertIs(result.get("result"), False)
745        self.assertIn("create_rest_api", result.get("comment", ""))
746
747    def test_present_when_stage_associating_to_new_deployment_errored_on_model_creation(
748        self,
749    ):
750        """
751        Tests creation of a new api/model/resource given nothing has been created previously,
752        and we failed on creating the models after successful creation of top level api object.
753        """
754        # no api existed
755        self.conn.get_rest_apis.return_value = no_apis_ret
756        # create top level api
757        self.conn.create_rest_api.return_value = api_ret
758        # no models existed in the newly created api
759        self.conn.get_model.side_effect = ClientError(error_content, "get_model")
760        # create model return error
761        self.conn.create_model.side_effect = ClientError(error_content, "create_model")
762
763        result = {}
764        with TempSwaggerFile() as swagger_file:
765            result = self.salt_states["boto_apigateway.present"](
766                "api present",
767                "unit test api",
768                swagger_file,
769                "test",
770                False,
771                "arn:aws:iam::1234:role/apigatewayrole",
772                **conn_parameters
773            )
774
775        self.assertIs(result.get("abort"), True)
776        self.assertIs(result.get("result"), False)
777        self.assertIn("create_model", result.get("comment", ""))
778
779    def test_present_when_stage_associating_to_new_deployment_errored_on_resource_creation(
780        self,
781    ):
782        """
783        Tests creation of a new api/model/resource given nothing has been created previously,
784        and we failed on creating the resource (paths) after successful creation of top level api/model
785        objects.
786        """
787        # no api existed
788        self.conn.get_rest_apis.return_value = no_apis_ret
789        # create top level api
790        self.conn.create_rest_api.return_value = api_ret
791        # no models existed in the newly created api
792        self.conn.get_model.side_effect = ClientError(error_content, "get_model")
793        # create model return values
794        self.conn.create_model.return_value = mock_model_ret
795        # get resources has nothing intiially except to the root path '/'
796        self.conn.get_resources.return_value = root_resources_ret
797        # create resources return error
798        self.conn.create_resource.side_effect = ClientError(
799            error_content, "create_resource"
800        )
801        result = {}
802        with TempSwaggerFile() as swagger_file:
803            result = self.salt_states["boto_apigateway.present"](
804                "api present",
805                "unit test api",
806                swagger_file,
807                "test",
808                False,
809                "arn:aws:iam::1234:role/apigatewayrole",
810                **conn_parameters
811            )
812        self.assertIs(result.get("abort"), True)
813        self.assertIs(result.get("result"), False)
814        self.assertIn("create_resource", result.get("comment", ""))
815
816    @pytest.mark.slow_test
817    def test_present_when_stage_associating_to_new_deployment_errored_on_put_method(
818        self,
819    ):
820        """
821        Tests creation of a new api/model/resource given nothing has been created previously,
822        and we failed on adding a post method to the resource after successful creation of top level
823        api, model, resource objects.
824        """
825        # no api existed
826        self.conn.get_rest_apis.return_value = no_apis_ret
827        # create top level api
828        self.conn.create_rest_api.return_value = api_ret
829        # no models existed in the newly created api
830        self.conn.get_model.side_effect = ClientError(error_content, "get_model")
831        # create model return values
832        self.conn.create_model.return_value = mock_model_ret
833        # various paths/resources already created
834        self.conn.get_resources.return_value = resources_ret
835        # the following should never be called
836        self.conn.create_resource.side_effect = ClientError(
837            error_content, "create_resource"
838        )
839
840        # create api method for POST
841        self.conn.put_method.side_effect = ClientError(error_content, "put_method")
842
843        result = {}
844        with patch.dict(
845            self.funcs,
846            {
847                "boto_lambda.describe_function": MagicMock(
848                    return_value={"function": function_ret}
849                )
850            },
851        ):
852            with TempSwaggerFile() as swagger_file:
853                result = self.salt_states["boto_apigateway.present"](
854                    "api present",
855                    "unit test api",
856                    swagger_file,
857                    "test",
858                    False,
859                    "arn:aws:iam::1234:role/apigatewayrole",
860                    **conn_parameters
861                )
862
863        self.assertIs(result.get("abort"), True)
864        self.assertIs(result.get("result"), False)
865        self.assertIn("put_method", result.get("comment", ""))
866
867    @pytest.mark.slow_test
868    def test_present_when_stage_associating_to_new_deployment_errored_on_lambda_function_lookup(
869        self,
870    ):
871        """
872        Tests creation of a new api/model/resource given nothing has been created previously,
873        and we failed on adding a post method due to a lamda look up failure after successful
874        creation of top level api, model, resource objects.
875        """
876        # no api existed
877        self.conn.get_rest_apis.return_value = no_apis_ret
878        # create top level api
879        self.conn.create_rest_api.return_value = api_ret
880        # no models existed in the newly created api
881        self.conn.get_model.side_effect = ClientError(error_content, "get_model")
882        # create model return values
883        self.conn.create_model.return_value = mock_model_ret
884        # various paths/resources already created
885        self.conn.get_resources.return_value = resources_ret
886        # the following should never be called
887        self.conn.create_resource.side_effect = ClientError(
888            error_content, "create_resource"
889        )
890        # create api method for POST
891        self.conn.put_method.return_value = method_ret
892        # create api method integration for POST
893        self.conn.put_integration.side_effect = ClientError(
894            error_content, "put_integration should not be invoked"
895        )
896
897        result = {}
898        with patch.dict(
899            self.funcs,
900            {
901                "boto_lambda.describe_function": MagicMock(
902                    return_value={"error": "no such lambda"}
903                )
904            },
905        ):
906            with TempSwaggerFile() as swagger_file:
907                result = self.salt_states["boto_apigateway.present"](
908                    "api present",
909                    "unit test api",
910                    swagger_file,
911                    "test",
912                    False,
913                    "arn:aws:iam::1234:role/apigatewayrole",
914                    **conn_parameters
915                )
916
917        self.assertIs(result.get("result"), False)
918        self.assertNotIn(
919            "put_integration should not be invoked", result.get("comment", "")
920        )
921        self.assertIn("not find lambda function", result.get("comment", ""))
922
923    @pytest.mark.slow_test
924    def test_present_when_stage_associating_to_new_deployment_errored_on_put_integration(
925        self,
926    ):
927        """
928        Tests creation of a new api/model/resource given nothing has been created previously,
929        and we failed on adding an integration for the post method to the resource after
930        successful creation of top level api, model, resource objects.
931        """
932        # no api existed
933        self.conn.get_rest_apis.return_value = no_apis_ret
934        # create top level api
935        self.conn.create_rest_api.return_value = api_ret
936        # no models existed in the newly created api
937        self.conn.get_model.side_effect = ClientError(error_content, "get_model")
938        # create model return values
939        self.conn.create_model.return_value = mock_model_ret
940        # various paths/resources already created
941        self.conn.get_resources.return_value = resources_ret
942        # the following should never be called
943        self.conn.create_resource.side_effect = ClientError(
944            error_content, "create_resource"
945        )
946
947        # create api method for POST
948        self.conn.put_method.return_value = method_ret
949        # create api method integration for POST
950        self.conn.put_integration.side_effect = ClientError(
951            error_content, "put_integration"
952        )
953
954        result = {}
955        with patch.dict(
956            self.funcs,
957            {
958                "boto_lambda.describe_function": MagicMock(
959                    return_value={"function": function_ret}
960                )
961            },
962        ):
963            with TempSwaggerFile() as swagger_file:
964                result = self.salt_states["boto_apigateway.present"](
965                    "api present",
966                    "unit test api",
967                    swagger_file,
968                    "test",
969                    False,
970                    "arn:aws:iam::1234:role/apigatewayrole",
971                    **conn_parameters
972                )
973
974        self.assertIs(result.get("abort"), True)
975        self.assertIs(result.get("result"), False)
976        self.assertIn("put_integration", result.get("comment", ""))
977
978    @pytest.mark.slow_test
979    def test_present_when_stage_associating_to_new_deployment_errored_on_put_method_response(
980        self,
981    ):
982        """
983        Tests creation of a new api/model/resource given nothing has been created previously,
984        and we failed on adding a method response for the post method to the resource after
985        successful creation of top level api, model, resource objects.
986        """
987        # no api existed
988        self.conn.get_rest_apis.return_value = no_apis_ret
989        # create top level api
990        self.conn.create_rest_api.return_value = api_ret
991        # no models existed in the newly created api
992        self.conn.get_model.side_effect = ClientError(error_content, "get_model")
993        # create model return values
994        self.conn.create_model.return_value = mock_model_ret
995        # various paths/resources already created
996        self.conn.get_resources.return_value = resources_ret
997        # the following should never be called
998        self.conn.create_resource.side_effect = ClientError(
999            error_content, "create_resource"
1000        )
1001
1002        # create api method for POST
1003        self.conn.put_method.return_value = method_ret
1004        # create api method integration for POST
1005        self.conn.put_integration.return_value = method_integration_ret
1006        # create api method response for POST/200
1007        self.conn.put_method_response.side_effect = ClientError(
1008            error_content, "put_method_response"
1009        )
1010
1011        result = {}
1012        with patch.dict(
1013            self.funcs,
1014            {
1015                "boto_lambda.describe_function": MagicMock(
1016                    return_value={"function": function_ret}
1017                )
1018            },
1019        ):
1020            with TempSwaggerFile() as swagger_file:
1021                result = self.salt_states["boto_apigateway.present"](
1022                    "api present",
1023                    "unit test api",
1024                    swagger_file,
1025                    "test",
1026                    False,
1027                    "arn:aws:iam::1234:role/apigatewayrole",
1028                    **conn_parameters
1029                )
1030
1031        self.assertIs(result.get("abort"), True)
1032        self.assertIs(result.get("result"), False)
1033        self.assertIn("put_method_response", result.get("comment", ""))
1034
1035    @pytest.mark.slow_test
1036    def test_present_when_stage_associating_to_new_deployment_errored_on_put_integration_response(
1037        self,
1038    ):
1039        """
1040        Tests creation of a new api/model/resource given nothing has been created previously,
1041        and we failed on adding an integration response for the post method to the resource after
1042        successful creation of top level api, model, resource objects.
1043        """
1044        # no api existed
1045        self.conn.get_rest_apis.return_value = no_apis_ret
1046        # create top level api
1047        self.conn.create_rest_api.return_value = api_ret
1048        # no models existed in the newly created api
1049        self.conn.get_model.side_effect = ClientError(error_content, "get_model")
1050        # create model return values
1051        self.conn.create_model.return_value = mock_model_ret
1052        # various paths/resources already created
1053        self.conn.get_resources.return_value = resources_ret
1054        # the following should never be called
1055        self.conn.create_resource.side_effect = ClientError(
1056            error_content, "create_resource"
1057        )
1058
1059        # create api method for POST
1060        self.conn.put_method.return_value = method_ret
1061        # create api method integration for POST
1062        self.conn.put_integration.return_value = method_integration_ret
1063        # create api method response for POST/200
1064        self.conn.put_method_response.return_value = method_response_200_ret
1065        # create api method integration response for POST
1066        self.conn.put_integration_response.side_effect = ClientError(
1067            error_content, "put_integration_response"
1068        )
1069
1070        result = {}
1071        with patch.dict(
1072            self.funcs,
1073            {
1074                "boto_lambda.describe_function": MagicMock(
1075                    return_value={"function": function_ret}
1076                )
1077            },
1078        ):
1079            with TempSwaggerFile() as swagger_file:
1080                result = self.salt_states["boto_apigateway.present"](
1081                    "api present",
1082                    "unit test api",
1083                    swagger_file,
1084                    "test",
1085                    False,
1086                    "arn:aws:iam::1234:role/apigatewayrole",
1087                    **conn_parameters
1088                )
1089
1090        self.assertIs(result.get("abort"), True)
1091        self.assertIs(result.get("result"), False)
1092        self.assertIn("put_integration_response", result.get("comment", ""))
1093
1094    def test_absent_when_rest_api_does_not_exist(self):
1095        """
1096        Tests scenario where the given api_name does not exist, absent state should return True
1097        with no changes.
1098        """
1099        self.conn.get_rest_apis.return_value = apis_ret
1100        self.conn.get_stage.side_effect = ClientError(
1101            error_content, "get_stage should not be called"
1102        )
1103
1104        result = self.salt_states["boto_apigateway.absent"](
1105            "api present",
1106            "no_such_rest_api",
1107            "no_such_stage",
1108            nuke_api=False,
1109            **conn_parameters
1110        )
1111
1112        self.assertIs(result.get("result"), True)
1113        self.assertNotIn("get_stage should not be called", result.get("comment", ""))
1114        self.assertEqual(result.get("changes"), {})
1115
1116    def test_absent_when_stage_is_invalid(self):
1117        """
1118        Tests scenario where the stagename doesn't exist
1119        """
1120        self.conn.get_rest_apis.return_value = apis_ret
1121        self.conn.get_stage.return_value = stage1_deployment1_ret
1122        self.conn.delete_stage.side_effect = ClientError(error_content, "delete_stage")
1123
1124        result = self.salt_states["boto_apigateway.absent"](
1125            "api present",
1126            "unit test api",
1127            "no_such_stage",
1128            nuke_api=False,
1129            **conn_parameters
1130        )
1131
1132        self.assertTrue(result.get("abort", False))
1133
1134    def test_absent_when_stage_is_valid_and_only_one_stage_is_associated_to_deployment(
1135        self,
1136    ):
1137        """
1138        Tests scenario where the stagename exists
1139        """
1140        self.conn.get_rest_apis.return_value = apis_ret
1141        self.conn.get_stage.return_value = stage1_deployment1_ret
1142        self.conn.delete_stage.return_value = {
1143            "ResponseMetadata": {
1144                "HTTPStatusCode": 200,
1145                "RequestId": "2d31072c-9d15-11e5-9977-6d9fcfda9c0a",
1146            }
1147        }
1148        self.conn.get_stages.return_value = no_stages_ret
1149        self.conn.delete_deployment.return_value = {
1150            "ResponseMetadata": {
1151                "HTTPStatusCode": 200,
1152                "RequestId": "2d31072c-9d15-11e5-9977-6d9fcfda9c0a",
1153            }
1154        }
1155
1156        result = self.salt_states["boto_apigateway.absent"](
1157            "api present", "unit test api", "test", nuke_api=False, **conn_parameters
1158        )
1159
1160        self.assertTrue(result.get("result", False))
1161
1162    def test_absent_when_stage_is_valid_and_two_stages_are_associated_to_deployment(
1163        self,
1164    ):
1165        """
1166        Tests scenario where the stagename exists and there are two stages associated with same deployment
1167        """
1168        self.conn.get_rest_apis.return_value = apis_ret
1169        self.conn.get_stage.return_value = stage1_deployment1_ret
1170        self.conn.delete_stage.return_value = {
1171            "ResponseMetadata": {
1172                "HTTPStatusCode": 200,
1173                "RequestId": "2d31072c-9d15-11e5-9977-6d9fcfda9c0a",
1174            }
1175        }
1176        self.conn.get_stages.return_value = stages_stage2_ret
1177
1178        result = self.salt_states["boto_apigateway.absent"](
1179            "api present", "unit test api", "test", nuke_api=False, **conn_parameters
1180        )
1181
1182        self.assertTrue(result.get("result", False))
1183
1184    def test_absent_when_failing_to_delete_a_deployment_no_longer_associated_with_any_stages(
1185        self,
1186    ):
1187        """
1188        Tests scenario where stagename exists and is deleted, but a failure occurs when trying to delete
1189        the deployment which is no longer associated to any other stages
1190        """
1191        self.conn.get_rest_apis.return_value = apis_ret
1192        self.conn.get_stage.return_value = stage1_deployment1_ret
1193        self.conn.delete_stage.return_value = {
1194            "ResponseMetadata": {
1195                "HTTPStatusCode": 200,
1196                "RequestId": "2d31072c-9d15-11e5-9977-6d9fcfda9c0a",
1197            }
1198        }
1199        self.conn.get_stages.return_value = no_stages_ret
1200        self.conn.delete_deployment.side_effect = ClientError(
1201            error_content, "delete_deployment"
1202        )
1203
1204        result = self.salt_states["boto_apigateway.absent"](
1205            "api present", "unit test api", "test", nuke_api=False, **conn_parameters
1206        )
1207
1208        self.assertTrue(result.get("abort", False))
1209
1210    def test_absent_when_nuke_api_and_no_more_stages_deployments_remain(self):
1211        """
1212        Tests scenario where the stagename exists and there are no stages associated with same deployment,
1213        the api would be deleted.
1214        """
1215        self.conn.get_rest_apis.return_value = apis_ret
1216        self.conn.get_stage.return_value = stage1_deployment1_ret
1217        self.conn.delete_stage.return_value = {
1218            "ResponseMetadata": {
1219                "HTTPStatusCode": 200,
1220                "RequestId": "2d31072c-9d15-11e5-9977-6d9fcfda9c0a",
1221            }
1222        }
1223        self.conn.get_stages.return_value = no_stages_ret
1224        self.conn.get_deployments.return_value = deployments_ret
1225        self.conn.delete_rest_api.return_value = {
1226            "ResponseMetadata": {
1227                "HTTPStatusCode": 200,
1228                "RequestId": "2d31072c-9d15-11e5-9977-6d9fcfda9c0a",
1229            }
1230        }
1231
1232        result = self.salt_states["boto_apigateway.absent"](
1233            "api present", "unit test api", "test", nuke_api=True, **conn_parameters
1234        )
1235
1236        self.assertIs(result.get("result"), True)
1237        self.assertIsNot(result.get("abort"), True)
1238        self.assertIs(
1239            result.get("changes", {})
1240            .get("new", [{}])[0]
1241            .get("delete_api", {})
1242            .get("deleted"),
1243            True,
1244        )
1245
1246    def test_absent_when_nuke_api_and_other_stages_deployments_exist(self):
1247        """
1248        Tests scenario where the stagename exists and there are two stages associated with same deployment,
1249        though nuke_api is requested, due to remaining deployments, we will not call the delete_rest_api call.
1250        """
1251        self.conn.get_rest_apis.return_value = apis_ret
1252        self.conn.get_stage.return_value = stage1_deployment1_ret
1253        self.conn.delete_stage.return_value = {
1254            "ResponseMetadata": {
1255                "HTTPStatusCode": 200,
1256                "RequestId": "2d31072c-9d15-11e5-9977-6d9fcfda9c0a",
1257            }
1258        }
1259        self.conn.get_stages.return_value = stages_stage2_ret
1260        self.conn.get_deployments.return_value = deployments_ret
1261        self.conn.delete_rest_api.side_effect = ClientError(
1262            error_content, "unexpected_api_delete"
1263        )
1264
1265        result = self.salt_states["boto_apigateway.absent"](
1266            "api present", "unit test api", "test", nuke_api=True, **conn_parameters
1267        )
1268
1269        self.assertIs(result.get("result"), True)
1270        self.assertIsNot(result.get("abort"), True)
1271
1272
1273@skipIf(HAS_BOTO is False, "The boto module must be installed.")
1274@skipIf(
1275    _has_required_boto() is False,
1276    "The boto3 module must be greater than or equal to version {}".format(
1277        required_boto3_version
1278    ),
1279)
1280@skipIf(
1281    _has_required_botocore() is False,
1282    "The botocore module must be greater than or equal to version {}".format(
1283        required_botocore_version
1284    ),
1285)
1286class BotoApiGatewayUsagePlanTestCase(
1287    BotoApiGatewayStateTestCaseBase, BotoApiGatewayTestCaseMixin
1288):
1289    """
1290    TestCase for salt.modules.boto_apigateway state.module, usage_plans portion
1291    """
1292
1293    @pytest.mark.slow_test
1294    def test_usage_plan_present_if_describe_fails(self, *args):
1295        """
1296        Tests correct error processing for describe_usage_plan failure
1297        """
1298        with patch.dict(
1299            boto_apigateway.__salt__,
1300            {
1301                "boto_apigateway.describe_usage_plans": MagicMock(
1302                    return_value={"error": "error"}
1303                )
1304            },
1305        ):
1306            result = boto_apigateway.usage_plan_present(
1307                "name", "plan_name", **conn_parameters
1308            )
1309
1310            self.assertIn("result", result)
1311            self.assertEqual(result["result"], False)
1312            self.assertIn("comment", result)
1313            self.assertEqual(
1314                result["comment"], "Failed to describe existing usage plans"
1315            )
1316            self.assertIn("changes", result)
1317            self.assertEqual(result["changes"], {})
1318
1319    @pytest.mark.slow_test
1320    def test_usage_plan_present_if_there_is_no_such_plan_and_test_option_is_set(
1321        self, *args
1322    ):
1323        """
1324        TestCse for salt.modules.boto_apigateway state.module, checking that if __opts__['test'] is set
1325        and usage plan does not exist, correct diagnostic will be returned
1326        """
1327        with patch.dict(boto_apigateway.__opts__, {"test": True}):
1328            with patch.dict(
1329                boto_apigateway.__salt__,
1330                {
1331                    "boto_apigateway.describe_usage_plans": MagicMock(
1332                        return_value={"plans": []}
1333                    )
1334                },
1335            ):
1336                result = boto_apigateway.usage_plan_present(
1337                    "name", "plan_name", **conn_parameters
1338                )
1339                self.assertIn("comment", result)
1340                self.assertEqual(
1341                    result["comment"], "a new usage plan plan_name would be created"
1342                )
1343                self.assertIn("result", result)
1344                self.assertEqual(result["result"], None)
1345
1346    @pytest.mark.slow_test
1347    def test_usage_plan_present_if_create_usage_plan_fails(self, *args):
1348        """
1349        Tests behavior for the case when creating a new usage plan fails
1350        """
1351        with patch.dict(boto_apigateway.__opts__, {"test": False}):
1352            with patch.dict(
1353                boto_apigateway.__salt__,
1354                {
1355                    "boto_apigateway.describe_usage_plans": MagicMock(
1356                        return_value={"plans": []}
1357                    ),
1358                    "boto_apigateway.create_usage_plan": MagicMock(
1359                        return_value={"error": "error"}
1360                    ),
1361                },
1362            ):
1363                result = boto_apigateway.usage_plan_present(
1364                    "name", "plan_name", **conn_parameters
1365                )
1366
1367                self.assertIn("result", result)
1368                self.assertEqual(result["result"], False)
1369                self.assertIn("comment", result)
1370                self.assertEqual(
1371                    result["comment"], "Failed to create a usage plan plan_name, error"
1372                )
1373                self.assertIn("changes", result)
1374                self.assertEqual(result["changes"], {})
1375
1376    @pytest.mark.slow_test
1377    def test_usage_plan_present_if_plan_is_there_and_needs_no_updates(self, *args):
1378        """
1379        Tests behavior for the case when plan is present and needs no updates
1380        """
1381        with patch.dict(boto_apigateway.__opts__, {"test": False}):
1382            with patch.dict(
1383                boto_apigateway.__salt__,
1384                {
1385                    "boto_apigateway.describe_usage_plans": MagicMock(
1386                        return_value={"plans": [{"id": "planid", "name": "planname"}]}
1387                    ),
1388                    "boto_apigateway.update_usage_plan": MagicMock(),
1389                },
1390            ):
1391                result = boto_apigateway.usage_plan_present(
1392                    "name", "plan_name", **conn_parameters
1393                )
1394
1395                self.assertIn("result", result)
1396                self.assertEqual(result["result"], True)
1397                self.assertIn("comment", result)
1398                self.assertEqual(
1399                    result["comment"],
1400                    "usage plan plan_name is already in a correct state",
1401                )
1402                self.assertIn("changes", result)
1403                self.assertEqual(result["changes"], {})
1404
1405                self.assertTrue(
1406                    boto_apigateway.__salt__[
1407                        "boto_apigateway.update_usage_plan"
1408                    ].call_count
1409                    == 0
1410                )
1411
1412    @pytest.mark.slow_test
1413    def test_usage_plan_present_if_plan_is_there_and_needs_updates_but_test_is_set(
1414        self, *args
1415    ):
1416        """
1417        Tests behavior when usage plan needs to be updated by tests option is set
1418        """
1419        with patch.dict(boto_apigateway.__opts__, {"test": True}):
1420            with patch.dict(
1421                boto_apigateway.__salt__,
1422                {
1423                    "boto_apigateway.describe_usage_plans": MagicMock(
1424                        return_value={
1425                            "plans": [
1426                                {
1427                                    "id": "planid",
1428                                    "name": "planname",
1429                                    "throttle": {"rateLimit": 10.0},
1430                                }
1431                            ]
1432                        }
1433                    ),
1434                    "boto_apigateway.update_usage_plan": MagicMock(),
1435                },
1436            ):
1437                result = boto_apigateway.usage_plan_present(
1438                    "name", "plan_name", **conn_parameters
1439                )
1440
1441                self.assertIn("comment", result)
1442                self.assertEqual(
1443                    result["comment"], "a new usage plan plan_name would be updated"
1444                )
1445                self.assertIn("result", result)
1446                self.assertEqual(result["result"], None)
1447                self.assertTrue(
1448                    boto_apigateway.__salt__[
1449                        "boto_apigateway.update_usage_plan"
1450                    ].call_count
1451                    == 0
1452                )
1453
1454    @pytest.mark.slow_test
1455    def test_usage_plan_present_if_plan_is_there_and_needs_updates_but_update_fails(
1456        self, *args
1457    ):
1458        """
1459        Tests error processing for the case when updating an existing usage plan fails
1460        """
1461        with patch.dict(boto_apigateway.__opts__, {"test": False}):
1462            with patch.dict(
1463                boto_apigateway.__salt__,
1464                {
1465                    "boto_apigateway.describe_usage_plans": MagicMock(
1466                        return_value={
1467                            "plans": [
1468                                {
1469                                    "id": "planid",
1470                                    "name": "planname",
1471                                    "throttle": {"rateLimit": 10.0},
1472                                }
1473                            ]
1474                        }
1475                    ),
1476                    "boto_apigateway.update_usage_plan": MagicMock(
1477                        return_value={"error": "error"}
1478                    ),
1479                },
1480            ):
1481                result = boto_apigateway.usage_plan_present(
1482                    "name", "plan_name", **conn_parameters
1483                )
1484
1485                self.assertIn("result", result)
1486                self.assertEqual(result["result"], False)
1487                self.assertIn("comment", result)
1488                self.assertEqual(
1489                    result["comment"], "Failed to update a usage plan plan_name, error"
1490                )
1491
1492    @pytest.mark.slow_test
1493    def test_usage_plan_present_if_plan_has_been_created(self, *args):
1494        """
1495        Tests successful case for creating a new usage plan
1496        """
1497        with patch.dict(boto_apigateway.__opts__, {"test": False}):
1498            with patch.dict(
1499                boto_apigateway.__salt__,
1500                {
1501                    "boto_apigateway.describe_usage_plans": MagicMock(
1502                        side_effect=[{"plans": []}, {"plans": [{"id": "id"}]}]
1503                    ),
1504                    "boto_apigateway.create_usage_plan": MagicMock(
1505                        return_value={"created": True}
1506                    ),
1507                },
1508            ):
1509                result = boto_apigateway.usage_plan_present(
1510                    "name", "plan_name", **conn_parameters
1511                )
1512
1513                self.assertIn("result", result)
1514                self.assertEqual(result["result"], True)
1515                self.assertIn("comment", result)
1516                self.assertEqual(
1517                    result["comment"], "A new usage plan plan_name has been created"
1518                )
1519                self.assertEqual(result["changes"]["old"], {"plan": None})
1520                self.assertEqual(result["changes"]["new"], {"plan": {"id": "id"}})
1521
1522    @pytest.mark.slow_test
1523    def test_usage_plan_present_if_plan_has_been_updated(self, *args):
1524        """
1525        Tests successful case for updating a usage plan
1526        """
1527        with patch.dict(boto_apigateway.__opts__, {"test": False}):
1528            with patch.dict(
1529                boto_apigateway.__salt__,
1530                {
1531                    "boto_apigateway.describe_usage_plans": MagicMock(
1532                        side_effect=[
1533                            {"plans": [{"id": "id"}]},
1534                            {
1535                                "plans": [
1536                                    {
1537                                        "id": "id",
1538                                        "throttle": {"rateLimit": throttle_rateLimit},
1539                                    }
1540                                ]
1541                            },
1542                        ]
1543                    ),
1544                    "boto_apigateway.update_usage_plan": MagicMock(
1545                        return_value={"updated": True}
1546                    ),
1547                },
1548            ):
1549                result = boto_apigateway.usage_plan_present(
1550                    "name",
1551                    "plan_name",
1552                    throttle={"rateLimit": throttle_rateLimit},
1553                    **conn_parameters
1554                )
1555
1556                self.assertIn("result", result)
1557                self.assertEqual(result["result"], True)
1558                self.assertIn("comment", result)
1559                self.assertEqual(
1560                    result["comment"], "usage plan plan_name has been updated"
1561                )
1562                self.assertEqual(result["changes"]["old"], {"plan": {"id": "id"}})
1563                self.assertEqual(
1564                    result["changes"]["new"],
1565                    {
1566                        "plan": {
1567                            "id": "id",
1568                            "throttle": {"rateLimit": throttle_rateLimit},
1569                        }
1570                    },
1571                )
1572
1573    @pytest.mark.slow_test
1574    def test_usage_plan_present_if_ValueError_is_raised(self, *args):
1575        """
1576        Tests error processing for the case when ValueError is raised when creating a usage plan
1577        """
1578        with patch.dict(
1579            boto_apigateway.__salt__,
1580            {
1581                "boto_apigateway.describe_usage_plans": MagicMock(
1582                    side_effect=ValueError("error")
1583                )
1584            },
1585        ):
1586            result = boto_apigateway.usage_plan_present(
1587                "name",
1588                "plan_name",
1589                throttle={"rateLimit": throttle_rateLimit},
1590                **conn_parameters
1591            )
1592
1593            self.assertIn("result", result)
1594            self.assertEqual(result["result"], False)
1595            self.assertIn("comment", result)
1596            self.assertEqual(result["comment"], repr(("error",)))
1597
1598    @pytest.mark.slow_test
1599    def test_usage_plan_present_if_IOError_is_raised(self, *args):
1600        """
1601        Tests error processing for the case when IOError is raised when creating a usage plan
1602        """
1603        with patch.dict(
1604            boto_apigateway.__salt__,
1605            {
1606                "boto_apigateway.describe_usage_plans": MagicMock(
1607                    side_effect=IOError("error")
1608                )
1609            },
1610        ):
1611            result = boto_apigateway.usage_plan_present(
1612                "name",
1613                "plan_name",
1614                throttle={"rateLimit": throttle_rateLimit},
1615                **conn_parameters
1616            )
1617
1618            self.assertIn("result", result)
1619            self.assertEqual(result["result"], False)
1620            self.assertIn("comment", result)
1621            self.assertEqual(result["comment"], repr(("error",)))
1622
1623    @pytest.mark.slow_test
1624    def test_usage_plan_absent_if_describe_fails(self, *args):
1625        """
1626        Tests correct error processing for describe_usage_plan failure
1627        """
1628        with patch.dict(
1629            boto_apigateway.__salt__,
1630            {
1631                "boto_apigateway.describe_usage_plans": MagicMock(
1632                    return_value={"error": "error"}
1633                )
1634            },
1635        ):
1636            result = {}
1637
1638            result = boto_apigateway.usage_plan_absent(
1639                "name", "plan_name", **conn_parameters
1640            )
1641
1642            self.assertIn("result", result)
1643            self.assertEqual(result["result"], False)
1644            self.assertIn("comment", result)
1645            self.assertEqual(
1646                result["comment"], "Failed to describe existing usage plans"
1647            )
1648            self.assertIn("changes", result)
1649            self.assertEqual(result["changes"], {})
1650
1651    @pytest.mark.slow_test
1652    def test_usage_plan_absent_if_plan_is_not_present(self, *args):
1653        """
1654        Tests behavior for the case when the plan that needs to be absent does not exist
1655        """
1656        with patch.dict(
1657            boto_apigateway.__salt__,
1658            {
1659                "boto_apigateway.describe_usage_plans": MagicMock(
1660                    return_value={"plans": []}
1661                )
1662            },
1663        ):
1664            result = {}
1665
1666            result = boto_apigateway.usage_plan_absent(
1667                "name", "plan_name", **conn_parameters
1668            )
1669
1670            self.assertIn("result", result)
1671            self.assertEqual(result["result"], True)
1672            self.assertIn("comment", result)
1673            self.assertEqual(
1674                result["comment"], "Usage plan plan_name does not exist already"
1675            )
1676            self.assertIn("changes", result)
1677            self.assertEqual(result["changes"], {})
1678
1679    @pytest.mark.slow_test
1680    def test_usage_plan_absent_if_plan_is_present_but_test_option_is_set(self, *args):
1681        """
1682        Tests behavior for the case when usage plan needs to be deleted by tests option is set
1683        """
1684        with patch.dict(boto_apigateway.__opts__, {"test": True}):
1685            with patch.dict(
1686                boto_apigateway.__salt__,
1687                {
1688                    "boto_apigateway.describe_usage_plans": MagicMock(
1689                        return_value={"plans": [{"id": "id"}]}
1690                    )
1691                },
1692            ):
1693                result = {}
1694
1695                result = boto_apigateway.usage_plan_absent(
1696                    "name", "plan_name", **conn_parameters
1697                )
1698
1699                self.assertIn("result", result)
1700                self.assertEqual(result["result"], None)
1701                self.assertIn("comment", result)
1702                self.assertEqual(
1703                    result["comment"],
1704                    "Usage plan plan_name exists and would be deleted",
1705                )
1706                self.assertIn("changes", result)
1707                self.assertEqual(result["changes"], {})
1708
1709    @pytest.mark.slow_test
1710    def test_usage_plan_absent_if_plan_is_present_but_delete_fails(self, *args):
1711        """
1712        Tests correct error processing when deleting a usage plan fails
1713        """
1714        with patch.dict(boto_apigateway.__opts__, {"test": False}):
1715            with patch.dict(
1716                boto_apigateway.__salt__,
1717                {
1718                    "boto_apigateway.describe_usage_plans": MagicMock(
1719                        return_value={"plans": [{"id": "id"}]}
1720                    ),
1721                    "boto_apigateway.delete_usage_plan": MagicMock(
1722                        return_value={"error": "error"}
1723                    ),
1724                },
1725            ):
1726                result = boto_apigateway.usage_plan_absent(
1727                    "name", "plan_name", **conn_parameters
1728                )
1729
1730                self.assertIn("result", result)
1731                self.assertEqual(result["result"], False)
1732                self.assertIn("comment", result)
1733                self.assertEqual(
1734                    result["comment"],
1735                    "Failed to delete usage plan plan_name, "
1736                    + repr({"error": "error"}),
1737                )
1738                self.assertIn("changes", result)
1739                self.assertEqual(result["changes"], {})
1740
1741    @pytest.mark.slow_test
1742    def test_usage_plan_absent_if_plan_has_been_deleted(self, *args):
1743        """
1744        Tests successful case for deleting a usage plan
1745        """
1746        with patch.dict(boto_apigateway.__opts__, {"test": False}):
1747            with patch.dict(
1748                boto_apigateway.__salt__,
1749                {
1750                    "boto_apigateway.describe_usage_plans": MagicMock(
1751                        return_value={"plans": [{"id": "id"}]}
1752                    ),
1753                    "boto_apigateway.delete_usage_plan": MagicMock(
1754                        return_value={"deleted": True}
1755                    ),
1756                },
1757            ):
1758                result = boto_apigateway.usage_plan_absent(
1759                    "name", "plan_name", **conn_parameters
1760                )
1761
1762                self.assertIn("result", result)
1763                self.assertEqual(result["result"], True)
1764                self.assertIn("comment", result)
1765                self.assertEqual(
1766                    result["comment"], "Usage plan plan_name has been deleted"
1767                )
1768                self.assertIn("changes", result)
1769                self.assertEqual(
1770                    result["changes"],
1771                    {"new": {"plan": None}, "old": {"plan": {"id": "id"}}},
1772                )
1773
1774    @pytest.mark.slow_test
1775    def test_usage_plan_absent_if_ValueError_is_raised(self, *args):
1776        """
1777        Tests correct error processing for the case when ValueError is raised when deleting a usage plan
1778        """
1779        with patch.dict(
1780            boto_apigateway.__salt__,
1781            {
1782                "boto_apigateway.describe_usage_plans": MagicMock(
1783                    side_effect=ValueError("error")
1784                )
1785            },
1786        ):
1787            result = boto_apigateway.usage_plan_absent(
1788                "name", "plan_name", **conn_parameters
1789            )
1790
1791            self.assertIn("result", result)
1792            self.assertEqual(result["result"], False)
1793            self.assertIn("comment", result)
1794            self.assertEqual(result["comment"], repr(("error",)))
1795
1796    @pytest.mark.slow_test
1797    def test_usage_plan_absent_if_IOError_is_raised(self, *args):
1798        """
1799        Tests correct error processing for the case when IOError is raised when deleting a usage plan
1800        """
1801        with patch.dict(
1802            boto_apigateway.__salt__,
1803            {
1804                "boto_apigateway.describe_usage_plans": MagicMock(
1805                    side_effect=IOError("error")
1806                )
1807            },
1808        ):
1809            result = boto_apigateway.usage_plan_absent(
1810                "name", "plan_name", **conn_parameters
1811            )
1812
1813            self.assertIn("result", result)
1814            self.assertEqual(result["result"], False)
1815            self.assertIn("comment", result)
1816            self.assertEqual(result["comment"], repr(("error",)))
1817
1818
1819@skipIf(HAS_BOTO is False, "The boto module must be installed.")
1820@skipIf(
1821    _has_required_boto() is False,
1822    "The boto3 module must be greater than or equal to version {}".format(
1823        required_boto3_version
1824    ),
1825)
1826@skipIf(
1827    _has_required_botocore() is False,
1828    "The botocore module must be greater than or equal to version {}".format(
1829        required_botocore_version
1830    ),
1831)
1832class BotoApiGatewayUsagePlanAssociationTestCase(
1833    BotoApiGatewayStateTestCaseBase, BotoApiGatewayTestCaseMixin
1834):
1835    """
1836    TestCase for salt.modules.boto_apigateway state.module, usage_plans_association portion
1837    """
1838
1839    @pytest.mark.slow_test
1840    def test_usage_plan_association_present_if_describe_fails(self, *args):
1841        """
1842        Tests correct error processing for describe_usage_plan failure
1843        """
1844        with patch.dict(
1845            boto_apigateway.__salt__,
1846            {
1847                "boto_apigateway.describe_usage_plans": MagicMock(
1848                    return_value={"error": "error"}
1849                )
1850            },
1851        ):
1852            result = boto_apigateway.usage_plan_association_present(
1853                "name", "plan_name", [association_stage_1], **conn_parameters
1854            )
1855
1856            self.assertIn("result", result)
1857            self.assertEqual(result["result"], False)
1858            self.assertIn("comment", result)
1859            self.assertEqual(
1860                result["comment"], "Failed to describe existing usage plans"
1861            )
1862            self.assertIn("changes", result)
1863            self.assertEqual(result["changes"], {})
1864
1865    @pytest.mark.slow_test
1866    def test_usage_plan_association_present_if_plan_is_not_present(self, *args):
1867        """
1868        Tests correct error processing if a plan for which association has been requested is not present
1869        """
1870        with patch.dict(
1871            boto_apigateway.__salt__,
1872            {
1873                "boto_apigateway.describe_usage_plans": MagicMock(
1874                    return_value={"plans": []}
1875                )
1876            },
1877        ):
1878            result = boto_apigateway.usage_plan_association_present(
1879                "name", "plan_name", [association_stage_1], **conn_parameters
1880            )
1881
1882            self.assertIn("result", result)
1883            self.assertEqual(result["result"], False)
1884            self.assertIn("comment", result)
1885            self.assertEqual(result["comment"], "Usage plan plan_name does not exist")
1886            self.assertIn("changes", result)
1887            self.assertEqual(result["changes"], {})
1888
1889    @pytest.mark.slow_test
1890    def test_usage_plan_association_present_if_multiple_plans_with_the_same_name_exist(
1891        self, *args
1892    ):
1893        """
1894        Tests correct error processing for the case when multiple plans with the same name exist
1895        """
1896        with patch.dict(
1897            boto_apigateway.__salt__,
1898            {
1899                "boto_apigateway.describe_usage_plans": MagicMock(
1900                    return_value={"plans": [{"id": "id1"}, {"id": "id2"}]}
1901                )
1902            },
1903        ):
1904            result = boto_apigateway.usage_plan_association_present(
1905                "name", "plan_name", [association_stage_1], **conn_parameters
1906            )
1907
1908            self.assertIn("result", result)
1909            self.assertEqual(result["result"], False)
1910            self.assertIn("comment", result)
1911            self.assertEqual(
1912                result["comment"],
1913                "There are multiple usage plans with the same name - it is not"
1914                " supported",
1915            )
1916            self.assertIn("changes", result)
1917            self.assertEqual(result["changes"], {})
1918
1919    @pytest.mark.slow_test
1920    def test_usage_plan_association_present_if_association_already_exists(self, *args):
1921        """
1922        Tests the behavior for the case when requested association is already present
1923        """
1924        with patch.dict(
1925            boto_apigateway.__salt__,
1926            {
1927                "boto_apigateway.describe_usage_plans": MagicMock(
1928                    return_value={
1929                        "plans": [{"id": "id1", "apiStages": [association_stage_1]}]
1930                    }
1931                )
1932            },
1933        ):
1934            result = boto_apigateway.usage_plan_association_present(
1935                "name", "plan_name", [association_stage_1], **conn_parameters
1936            )
1937
1938            self.assertIn("result", result)
1939            self.assertEqual(result["result"], True)
1940            self.assertIn("comment", result)
1941            self.assertEqual(
1942                result["comment"], "Usage plan is already asssociated to all api stages"
1943            )
1944            self.assertIn("changes", result)
1945            self.assertEqual(result["changes"], {})
1946
1947    @pytest.mark.slow_test
1948    def test_usage_plan_association_present_if_update_fails(self, *args):
1949        """
1950        Tests correct error processing for the case when adding associations fails
1951        """
1952        with patch.dict(
1953            boto_apigateway.__salt__,
1954            {
1955                "boto_apigateway.describe_usage_plans": MagicMock(
1956                    return_value={
1957                        "plans": [{"id": "id1", "apiStages": [association_stage_1]}]
1958                    }
1959                ),
1960                "boto_apigateway.attach_usage_plan_to_apis": MagicMock(
1961                    return_value={"error": "error"}
1962                ),
1963            },
1964        ):
1965            result = boto_apigateway.usage_plan_association_present(
1966                "name", "plan_name", [association_stage_2], **conn_parameters
1967            )
1968
1969            self.assertIn("result", result)
1970            self.assertEqual(result["result"], False)
1971            self.assertIn("comment", result)
1972            self.assertTrue(
1973                result["comment"].startswith("Failed to associate a usage plan")
1974            )
1975            self.assertIn("changes", result)
1976            self.assertEqual(result["changes"], {})
1977
1978    @pytest.mark.slow_test
1979    def test_usage_plan_association_present_success(self, *args):
1980        """
1981        Tests successful case for adding usage plan associations to a given api stage
1982        """
1983        with patch.dict(
1984            boto_apigateway.__salt__,
1985            {
1986                "boto_apigateway.describe_usage_plans": MagicMock(
1987                    return_value={
1988                        "plans": [{"id": "id1", "apiStages": [association_stage_1]}]
1989                    }
1990                ),
1991                "boto_apigateway.attach_usage_plan_to_apis": MagicMock(
1992                    return_value={
1993                        "result": {
1994                            "apiStages": [association_stage_1, association_stage_2]
1995                        }
1996                    }
1997                ),
1998            },
1999        ):
2000            result = boto_apigateway.usage_plan_association_present(
2001                "name", "plan_name", [association_stage_2], **conn_parameters
2002            )
2003
2004            self.assertIn("result", result)
2005            self.assertEqual(result["result"], True)
2006            self.assertIn("comment", result)
2007            self.assertEqual(
2008                result["comment"], "successfully associated usage plan to apis"
2009            )
2010            self.assertIn("changes", result)
2011            self.assertEqual(
2012                result["changes"],
2013                {
2014                    "new": [association_stage_1, association_stage_2],
2015                    "old": [association_stage_1],
2016                },
2017            )
2018
2019    @pytest.mark.slow_test
2020    def test_usage_plan_association_present_if_value_error_is_thrown(self, *args):
2021        """
2022        Tests correct error processing for the case when IOError is raised while trying to set usage plan associations
2023        """
2024        with patch.dict(
2025            boto_apigateway.__salt__,
2026            {
2027                "boto_apigateway.describe_usage_plans": MagicMock(
2028                    side_effect=ValueError("error")
2029                )
2030            },
2031        ):
2032            result = boto_apigateway.usage_plan_association_present(
2033                "name", "plan_name", [], **conn_parameters
2034            )
2035
2036            self.assertIn("result", result)
2037            self.assertEqual(result["result"], False)
2038            self.assertIn("comment", result)
2039            self.assertEqual(result["comment"], repr(("error",)))
2040            self.assertIn("changes", result)
2041            self.assertEqual(result["changes"], {})
2042
2043    @pytest.mark.slow_test
2044    def test_usage_plan_association_present_if_io_error_is_thrown(self, *args):
2045        """
2046        Tests correct error processing for the case when IOError is raised while trying to set usage plan associations
2047        """
2048        with patch.dict(
2049            boto_apigateway.__salt__,
2050            {
2051                "boto_apigateway.describe_usage_plans": MagicMock(
2052                    side_effect=IOError("error")
2053                )
2054            },
2055        ):
2056            result = boto_apigateway.usage_plan_association_present(
2057                "name", "plan_name", [], **conn_parameters
2058            )
2059
2060            self.assertIn("result", result)
2061            self.assertEqual(result["result"], False)
2062            self.assertIn("comment", result)
2063            self.assertEqual(result["comment"], repr(("error",)))
2064            self.assertIn("changes", result)
2065            self.assertEqual(result["changes"], {})
2066
2067    @pytest.mark.slow_test
2068    def test_usage_plan_association_absent_if_describe_fails(self, *args):
2069        """
2070        Tests correct error processing for describe_usage_plan failure
2071        """
2072        with patch.dict(
2073            boto_apigateway.__salt__,
2074            {
2075                "boto_apigateway.describe_usage_plans": MagicMock(
2076                    return_value={"error": "error"}
2077                )
2078            },
2079        ):
2080            result = boto_apigateway.usage_plan_association_absent(
2081                "name", "plan_name", [association_stage_1], **conn_parameters
2082            )
2083            self.assertIn("result", result)
2084            self.assertEqual(result["result"], False)
2085            self.assertIn("comment", result)
2086            self.assertEqual(
2087                result["comment"], "Failed to describe existing usage plans"
2088            )
2089            self.assertIn("changes", result)
2090            self.assertEqual(result["changes"], {})
2091
2092    @pytest.mark.slow_test
2093    def test_usage_plan_association_absent_if_plan_is_not_present(self, *args):
2094        """
2095        Tests error processing for the case when plan for which associations need to be modified is not present
2096        """
2097        with patch.dict(
2098            boto_apigateway.__salt__,
2099            {
2100                "boto_apigateway.describe_usage_plans": MagicMock(
2101                    return_value={"plans": []}
2102                )
2103            },
2104        ):
2105            result = boto_apigateway.usage_plan_association_absent(
2106                "name", "plan_name", [association_stage_1], **conn_parameters
2107            )
2108
2109            self.assertIn("result", result)
2110            self.assertEqual(result["result"], False)
2111            self.assertIn("comment", result)
2112            self.assertEqual(result["comment"], "Usage plan plan_name does not exist")
2113            self.assertIn("changes", result)
2114            self.assertEqual(result["changes"], {})
2115
2116    @pytest.mark.slow_test
2117    def test_usage_plan_association_absent_if_multiple_plans_with_the_same_name_exist(
2118        self, *args
2119    ):
2120        """
2121        Tests the case when there are multiple plans with the same name but different Ids
2122        """
2123        with patch.dict(
2124            boto_apigateway.__salt__,
2125            {
2126                "boto_apigateway.describe_usage_plans": MagicMock(
2127                    return_value={"plans": [{"id": "id1"}, {"id": "id2"}]}
2128                )
2129            },
2130        ):
2131            result = boto_apigateway.usage_plan_association_absent(
2132                "name", "plan_name", [association_stage_1], **conn_parameters
2133            )
2134
2135            self.assertIn("result", result)
2136            self.assertEqual(result["result"], False)
2137            self.assertIn("comment", result)
2138            self.assertEqual(
2139                result["comment"],
2140                "There are multiple usage plans with the same name - it is not"
2141                " supported",
2142            )
2143            self.assertIn("changes", result)
2144            self.assertEqual(result["changes"], {})
2145
2146    @pytest.mark.slow_test
2147    def test_usage_plan_association_absent_if_plan_has_no_associations(self, *args):
2148        """
2149        Tests the case when the plan has no associations at all
2150        """
2151        with patch.dict(
2152            boto_apigateway.__salt__,
2153            {
2154                "boto_apigateway.describe_usage_plans": MagicMock(
2155                    return_value={"plans": [{"id": "id1", "apiStages": []}]}
2156                )
2157            },
2158        ):
2159            result = boto_apigateway.usage_plan_association_absent(
2160                "name", "plan_name", [association_stage_1], **conn_parameters
2161            )
2162
2163            self.assertIn("result", result)
2164            self.assertEqual(result["result"], True)
2165            self.assertIn("comment", result)
2166            self.assertEqual(
2167                result["comment"],
2168                "Usage plan plan_name has no associated stages already",
2169            )
2170            self.assertIn("changes", result)
2171            self.assertEqual(result["changes"], {})
2172
2173    @pytest.mark.slow_test
2174    def test_usage_plan_association_absent_if_plan_has_no_specific_association(
2175        self, *args
2176    ):
2177        """
2178        Tests the case when requested association is not present already
2179        """
2180        with patch.dict(
2181            boto_apigateway.__salt__,
2182            {
2183                "boto_apigateway.describe_usage_plans": MagicMock(
2184                    return_value={
2185                        "plans": [{"id": "id1", "apiStages": [association_stage_1]}]
2186                    }
2187                )
2188            },
2189        ):
2190            result = boto_apigateway.usage_plan_association_absent(
2191                "name", "plan_name", [association_stage_2], **conn_parameters
2192            )
2193
2194            self.assertIn("result", result)
2195            self.assertEqual(result["result"], True)
2196            self.assertIn("comment", result)
2197            self.assertEqual(
2198                result["comment"],
2199                "Usage plan is already not asssociated to any api stages",
2200            )
2201            self.assertIn("changes", result)
2202            self.assertEqual(result["changes"], {})
2203
2204    @pytest.mark.slow_test
2205    def test_usage_plan_association_absent_if_detaching_association_fails(self, *args):
2206        """
2207        Tests correct error processing when detaching the usage plan from the api function is called
2208        """
2209        with patch.dict(
2210            boto_apigateway.__salt__,
2211            {
2212                "boto_apigateway.describe_usage_plans": MagicMock(
2213                    return_value={
2214                        "plans": [
2215                            {
2216                                "id": "id1",
2217                                "apiStages": [association_stage_1, association_stage_2],
2218                            }
2219                        ]
2220                    }
2221                ),
2222                "boto_apigateway.detach_usage_plan_from_apis": MagicMock(
2223                    return_value={"error": "error"}
2224                ),
2225            },
2226        ):
2227            result = boto_apigateway.usage_plan_association_absent(
2228                "name", "plan_name", [association_stage_2], **conn_parameters
2229            )
2230
2231            self.assertIn("result", result)
2232            self.assertEqual(result["result"], False)
2233            self.assertIn("comment", result)
2234            self.assertTrue(
2235                result["comment"].startswith(
2236                    "Failed to disassociate a usage plan plan_name from the apis"
2237                )
2238            )
2239            self.assertIn("changes", result)
2240            self.assertEqual(result["changes"], {})
2241
2242    @pytest.mark.slow_test
2243    def test_usage_plan_association_absent_success(self, *args):
2244        """
2245        Tests successful case of disaccosiation the usage plan from api stages
2246        """
2247        with patch.dict(
2248            boto_apigateway.__salt__,
2249            {
2250                "boto_apigateway.describe_usage_plans": MagicMock(
2251                    return_value={
2252                        "plans": [
2253                            {
2254                                "id": "id1",
2255                                "apiStages": [association_stage_1, association_stage_2],
2256                            }
2257                        ]
2258                    }
2259                ),
2260                "boto_apigateway.detach_usage_plan_from_apis": MagicMock(
2261                    return_value={"result": {"apiStages": [association_stage_1]}}
2262                ),
2263            },
2264        ):
2265            result = boto_apigateway.usage_plan_association_absent(
2266                "name", "plan_name", [association_stage_2], **conn_parameters
2267            )
2268
2269            self.assertIn("result", result)
2270            self.assertEqual(result["result"], True)
2271            self.assertIn("comment", result)
2272            self.assertEqual(
2273                result["comment"], "successfully disassociated usage plan from apis"
2274            )
2275            self.assertIn("changes", result)
2276            self.assertEqual(
2277                result["changes"],
2278                {
2279                    "new": [association_stage_1],
2280                    "old": [association_stage_1, association_stage_2],
2281                },
2282            )
2283
2284    @pytest.mark.slow_test
2285    def test_usage_plan_association_absent_if_ValueError_is_raised(self, *args):
2286        """
2287        Tests correct error processing for the case where ValueError is raised while trying to remove plan associations
2288        """
2289        with patch.dict(
2290            boto_apigateway.__salt__,
2291            {
2292                "boto_apigateway.describe_usage_plans": MagicMock(
2293                    side_effect=ValueError("error")
2294                )
2295            },
2296        ):
2297            result = boto_apigateway.usage_plan_association_absent(
2298                "name", "plan_name", [association_stage_1], **conn_parameters
2299            )
2300
2301            self.assertIn("result", result)
2302            self.assertEqual(result["result"], False)
2303            self.assertIn("comment", result)
2304            self.assertEqual(result["comment"], repr(("error",)))
2305            self.assertIn("changes", result)
2306            self.assertEqual(result["changes"], {})
2307
2308    @pytest.mark.slow_test
2309    def test_usage_plan_association_absent_if_IOError_is_raised(self, *args):
2310        """
2311        Tests correct error processing for the case where IOError exception is raised while trying to remove plan associations
2312        """
2313        with patch.dict(
2314            boto_apigateway.__salt__,
2315            {
2316                "boto_apigateway.describe_usage_plans": MagicMock(
2317                    side_effect=IOError("error")
2318                )
2319            },
2320        ):
2321            result = boto_apigateway.usage_plan_association_absent(
2322                "name", "plan_name", [association_stage_1], **conn_parameters
2323            )
2324
2325            self.assertIn("result", result)
2326            self.assertEqual(result["result"], False)
2327            self.assertIn("comment", result)
2328            self.assertEqual(result["comment"], repr(("error",)))
2329            self.assertIn("changes", result)
2330            self.assertEqual(result["changes"], {})
2331