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