|  | 
| 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