|
14 | 14 | OperationStatus,
|
15 | 15 | ProgressEvent,
|
16 | 16 | )
|
17 |
| -from cloudformation_cli_python_lib.utils import Credentials, HookInvocationRequest |
| 17 | +from cloudformation_cli_python_lib.utils import ( |
| 18 | + Credentials, |
| 19 | + HookInvocationRequest, |
| 20 | + HookRequestData, |
| 21 | +) |
18 | 22 |
|
19 | 23 | import json
|
20 | 24 | from datetime import datetime
|
| 25 | +from typing import Any, Mapping |
21 | 26 | from unittest.mock import Mock, call, patch, sentinel
|
22 | 27 |
|
23 | 28 | ENTRYPOINT_PAYLOAD = {
|
|
50 | 55 | "hookModel": sentinel.type_configuration,
|
51 | 56 | }
|
52 | 57 |
|
| 58 | +STACK_LEVEL_HOOK_ENTRYPOINT_PAYLOAD = { |
| 59 | + "awsAccountId": "123456789012", |
| 60 | + "clientRequestToken": "4b90a7e4-b790-456b-a937-0cfdfa211dfe", |
| 61 | + "region": "us-east-1", |
| 62 | + "actionInvocationPoint": "CREATE_PRE_PROVISION", |
| 63 | + "hookTypeName": "AWS::Test::TestHook", |
| 64 | + "hookTypeVersion": "1.0", |
| 65 | + "requestContext": { |
| 66 | + "invocation": 1, |
| 67 | + "callbackContext": {}, |
| 68 | + }, |
| 69 | + "requestData": { |
| 70 | + "callerCredentials": '{"accessKeyId": "IASAYK835GAIFHAHEI23", "secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5poh2O0", "sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg"}', # noqa: B950 |
| 71 | + "providerCredentials": '{"accessKeyId": "HDI0745692Y45IUTYR78", "secretAccessKey": "4976TUYVI2345GW87ERYG823RF87GY9EIUH452I3", "sessionToken": "842HYOFIQAEUDF78R8T7IU43HSADYGIFHBJSDHFA87SDF9PYvN1CEYASDUYFT5TQ97YASIHUDFAIUEYRISDKJHFAYSUDTFSDFADS"}', # noqa: B950 |
| 72 | + "providerLogGroupName": "providerLoggingGroupName", |
| 73 | + "targetName": "STACK", |
| 74 | + "targetType": "STACK", |
| 75 | + "targetLogicalId": "myStack", |
| 76 | + "hookEncryptionKeyArn": None, |
| 77 | + "hookEncryptionKeyRole": None, |
| 78 | + "payload": "https://someS3PresignedURL", |
| 79 | + "targetModel": {}, |
| 80 | + }, |
| 81 | + "stackId": "arn:aws:cloudformation:us-east-1:123456789012:stack/SampleStack/e" |
| 82 | + "722ae60-fe62-11e8-9a0e-0ae8cc519968", |
| 83 | + "hookModel": sentinel.type_configuration, |
| 84 | +} |
| 85 | + |
53 | 86 |
|
54 | 87 | TYPE_NAME = "Test::Foo::Bar"
|
55 | 88 |
|
@@ -452,7 +485,78 @@ def test_test_entrypoint_success():
|
452 | 485 | (OperationStatus.IN_PROGRESS, HookStatus.IN_PROGRESS),
|
453 | 486 | (OperationStatus.SUCCESS, HookStatus.SUCCESS),
|
454 | 487 | (OperationStatus.FAILED, HookStatus.FAILED),
|
| 488 | + ( |
| 489 | + OperationStatus.CHANGE_SET_SUCCESS_SKIP_STACK_HOOK, |
| 490 | + HookStatus.CHANGE_SET_SUCCESS_SKIP_STACK_HOOK, |
| 491 | + ), |
455 | 492 | ],
|
456 | 493 | )
|
457 | 494 | def test_get_hook_status(operation_status, hook_status):
|
458 | 495 | assert hook_status == Hook._get_hook_status(operation_status)
|
| 496 | + |
| 497 | + |
| 498 | +def test__hook_request_data_remote_payload(): |
| 499 | + non_remote_input = HookRequestData( |
| 500 | + targetName="someTargetName", |
| 501 | + targetType="someTargetModel", |
| 502 | + targetLogicalId="someTargetLogicalId", |
| 503 | + targetModel={"resourceProperties": {"propKeyA": "propValueA"}}, |
| 504 | + ) |
| 505 | + assert non_remote_input.is_hook_invocation_payload_remote() is False |
| 506 | + |
| 507 | + non_remote_input_1 = HookRequestData( |
| 508 | + targetName="someTargetName", |
| 509 | + targetType="someTargetModel", |
| 510 | + targetLogicalId="someTargetLogicalId", |
| 511 | + targetModel={"resourceProperties": {"propKeyA": "propValueA"}}, |
| 512 | + payload="https://someUrl", |
| 513 | + ) |
| 514 | + assert non_remote_input_1.is_hook_invocation_payload_remote() is False |
| 515 | + |
| 516 | + remote_input = HookRequestData( |
| 517 | + targetName="someTargetName", |
| 518 | + targetType="someTargetModel", |
| 519 | + targetLogicalId="someTargetLogicalId", |
| 520 | + targetModel={}, |
| 521 | + payload="https://someUrl", |
| 522 | + ) |
| 523 | + assert remote_input.is_hook_invocation_payload_remote() is True |
| 524 | + |
| 525 | + |
| 526 | +def test__test_stack_level_hook_input(hook): |
| 527 | + hook = Hook(TYPE_NAME, Mock()) |
| 528 | + |
| 529 | + with patch( |
| 530 | + "cloudformation_cli_python_lib.utils.requests.Session.get" |
| 531 | + ) as mock_requests_lib: |
| 532 | + mock_requests_lib.return_value = MockResponse(200, {"foo": "bar"}) |
| 533 | + _, _, _, req = hook._parse_request(STACK_LEVEL_HOOK_ENTRYPOINT_PAYLOAD) |
| 534 | + |
| 535 | + assert req.requestData.targetName == "STACK" |
| 536 | + assert req.requestData.targetType == "STACK" |
| 537 | + assert req.requestData.targetLogicalId == "myStack" |
| 538 | + assert req.requestData.targetModel == {"foo": "bar"} |
| 539 | + |
| 540 | + |
| 541 | +def test__test_stack_level_hook_input_failed_s3_download(hook): |
| 542 | + hook = Hook(TYPE_NAME, Mock()) |
| 543 | + |
| 544 | + with patch( |
| 545 | + "cloudformation_cli_python_lib.utils.requests.Session.get" |
| 546 | + ) as mock_requests_lib: |
| 547 | + mock_requests_lib.return_value = MockResponse(404, {"foo": "bar"}) |
| 548 | + _, _, _, req = hook._parse_request(STACK_LEVEL_HOOK_ENTRYPOINT_PAYLOAD) |
| 549 | + |
| 550 | + assert req.requestData.targetName == "STACK" |
| 551 | + assert req.requestData.targetType == "STACK" |
| 552 | + assert req.requestData.targetLogicalId == "myStack" |
| 553 | + assert req.requestData.targetModel == {} |
| 554 | + |
| 555 | + |
| 556 | +@dataclass |
| 557 | +class MockResponse: |
| 558 | + status_code: int |
| 559 | + _json: Mapping[str, Any] |
| 560 | + |
| 561 | + def json(self) -> Mapping[str, Any]: |
| 562 | + return self._json |
0 commit comments